Skip to main content

iOS implementation

Let's use XCode, to write iOS code. Open XCode, by running this command from the root directory of your app:

xed ios

When workspace is opened, locate Pods project and expand it. Search for Development Pods and find RangeSliderPackage inside. When it's expanded, it will show all files that we created under range-slider-package/ios directory.

Add native library as dependency for the package

When developing some native code, you might end up in a need to add some iOS library. Each RN library that includes some iOS native code is integrated to your project thanks to CocoaPods. And that's also the case for our module - we have RangeSliderPackage.podspec, so let's navigate there and add a native dependency.

RangeSliderPackage.podspec
# `.podspec` file is like "`package.json`" for iOS CocoaPods packages

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

# Detect if new arch is enabled
new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'

Pod::Spec.new do |s|
s.name = "RangeSliderPackage"
s.version = package["version"]
s.summary = package["description"]
s.description = package["description"]
s.homepage = package["homepage"]
s.license = package["license"]
s.platforms = { :ios => "13.0" }
s.author = package["author"]
s.source = { :git => package["repository"], :tag => "#{s.version}" }

# This is crucial - declare which files will be included in the package (similar to "files" field in `package.json`)
s.source_files = "ios/**/*.{h,m,mm,swift}"

+ # Declare dependency (similar to entries under "dependencies" field in `package.json`)
+ s.dependency 'RangeUISlider', '~> 3.0'

if new_arch_enabled
s.pod_target_xcconfig = {
"DEFINES_MODULE" => "YES",
"SWIFT_OBJC_INTERFACE_HEADER_NAME" => "RangeSliderPackage-Swift.h",
# This is handy when we want to detect if new arch is enabled in Swift code
# and can be used like:
# #if RANGE_SLIDER_PACKAGE_NEW_ARCH_ENABLED
# // do sth when new arch is enabled
# #else
# // do sth when old arch is enabled
# #endif
"OTHER_SWIFT_FLAGS" => "-DRANGE_SLIDER_PACKAGE_NEW_ARCH_ENABLED"
}
else
s.pod_target_xcconfig = {
"DEFINES_MODULE" => "YES",
"SWIFT_OBJC_INTERFACE_HEADER_NAME" => "RangeSliderPackage-Swift.h"
}
end

# Install all React Native dependencies (RN >= 0.71 must be used)
#
# check source code for more context
# https://github.com/facebook/react-native/blob/0.71-stable/scripts/react_native_pods.rb#L172#L180
install_modules_dependencies(s)
end

For the range slider view we will use RangeUISlider library. To declare the dependency in the podspec, we add s.dependency "<name of the native library>" - in this case it's s.dependency 'RangeUISlider', '~> 3.0'.

info

'~> 3.0' syntax in the podspec is kind of similar to declaring some JS dependency in package.json with version "^3" - so the version needs to be at least 3.0.0, but less than 4.0.0

After that, you can run npx pod-install from your app's directory and library should be installed.

RangeSliderView.swift

We will start by defining the boilerplate for our custom view:

ios/RangeSliderView.swift
import UIKit

@objc(RangeSliderViewDelegate)
public protocol RangeSliderViewDelegate {
func sendOnRangeSliderViewBeginDragEvent()
func sendOnRangeSliderViewEndDragEvent(minValue: Double, maxValue: Double)
func sendOnRangeSliderViewValueChangeEvent(minValue: Double, maxValue: Double)
}

@objc(RangeSliderView)
public class RangeSliderView: UIView {
@objc public weak var delegate: RangeSliderViewDelegate? = nil

@objc public var onRangeSliderViewBeginDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewEndDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewValueChange: ((NSDictionary?) -> Void)? = nil

@objc public var activeColor: UIColor = UIColor.systemBlue

@objc public var inactiveColor: UIColor = UIColor.systemGray

@objc public var minValue: Double = 0.0

@objc public var maxValue: Double = 1.0

@objc public var leftKnobValue: Double = 0.0

@objc public var rightKnobValue: Double = 1.0

@objc public var step: Int = 0
}

We'll export 2 things to the Objective-C world - our view and its delegate protocol. For now all view's properties have only some default values, but we will add some code later to forward them to the slider.

info

To make Swift elements accessible to Objective-C world, we have to do 4 things:

  • make class extending NSObject
  • mark class and its methods (at least those methods that are meant to be exposed) as public
  • mark class with @objc(exported-objc-name) decorator
  • mark exposed methods with @objc decorator

Next step is to create the slider - will wrap the slider in the object class that will keep the slider and react to the slider's events.

Add the following snippet at the top of the file:

ios/RangeSliderView.swift
import RangeUISlider
import UIKit

class RangeUISliderObject {
public weak var delegate: RangeSliderViewDelegate? = nil

public var slider: RangeUISlider = {
let rangeSlider = RangeUISlider()
rangeSlider.barCorners = 5
rangeSlider.barHeight = 10
rangeSlider.leftKnobColor = UIColor.systemBlue
rangeSlider.leftKnobCorners = 10
rangeSlider.leftKnobHeight = 20
rangeSlider.leftKnobWidth = 20
rangeSlider.rightKnobColor = UIColor.systemBlue
rangeSlider.rightKnobCorners = 10
rangeSlider.rightKnobHeight = 20
rangeSlider.rightKnobWidth = 20
rangeSlider.showKnobsLabels = true
return rangeSlider
}()

private var isDragging: Bool = false

init() {
self.slider.delegate = self
}
}

extension RangeUISliderObject: RangeUISliderDelegate {
public func rangeIsChanging(minValueSelected: CGFloat, maxValueSelected: CGFloat, slider: RangeUISlider) {
self.delegate?.sendOnRangeSliderViewValueChangeEvent(minValue: minValueSelected, maxValue: maxValueSelected)
}

public func rangeChangeFinished(minValueSelected: CGFloat, maxValueSelected: CGFloat, slider: RangeUISlider) {
if !isDragging {
return
}
self.delegate?.sendOnRangeSliderViewEndDragEvent(minValue: minValueSelected, maxValue: maxValueSelected)
self.isDragging = false
}

public func rangeChangeStarted() {
self.isDragging = true
self.delegate?.sendOnRangeSliderViewBeginDragEvent()
}
}

So first we create RangeUISliderObject class, which will hold our slider. The slider property is created with some default configuration assigned to the slider. This object class will also react to the events emitted by slider - we do it by extending the object with RangeUISliderDelegate protocol and setting slider's delegate property to this object's instance. And to make sure delegate methods are fired only when slider is dragged, we use isDragging flag.

tip

For more about delegates and Delegate Pattern, see Apple's docs.

So now, let's try to combine them.

ios/RangeUISliderWrapper.swift
// ...

@objc(RangeSliderView)
public class RangeSliderView: UIView {
// ...

private var sliderObject = RangeUISliderObject()

public override init(frame: CGRect) {
super.init(frame: frame)
self.sliderObject.delegate = self.delegate
self.addSubview(self.sliderObject.slider)

self.sliderObject.slider.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.sliderObject.slider.topAnchor.constraint(equalTo: self.topAnchor),
self.sliderObject.slider.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.sliderObject.slider.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.sliderObject.slider.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// ...
}

Here, we're initializing slider object and the slider is added as a subview.

The slider object is created in the view's init(frame:) initializer, where we also add the slider as a subview. The slider subview needs to match its parent size, we do it by constraining its anchors to parent anchors. For more on layout constraints, visit Auto Layout anchors section in Apple's docs.

The only thing left is to forward all properties to the slider:

ios/RangeUISliderWrapper.swift
// ...

@objc(RangeSliderView)
public class RangeSliderView: UIView {
@objc public weak var delegate: RangeSliderViewDelegate? = nil {
didSet {
sliderObject.delegate = delegate
}
}

@objc public var onRangeSliderViewBeginDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewEndDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewValueChange: ((NSDictionary?) -> Void)? = nil

private var sliderObject = RangeUISliderObject()

public override init(frame: CGRect) {
super.init(frame: frame)
self.sliderObject.delegate = self.delegate
self.addSubview(self.sliderObject.slider)

self.sliderObject.slider.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.sliderObject.slider.topAnchor.constraint(equalTo: self.topAnchor),
self.sliderObject.slider.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.sliderObject.slider.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.sliderObject.slider.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc public var activeColor: UIColor = UIColor.systemBlue {
didSet {
sliderObject.slider.rangeSelectedColor = activeColor
}
}

@objc public var inactiveColor: UIColor = UIColor.systemGray {
didSet {
sliderObject.slider.rangeNotSelectedColor = inactiveColor
}
}

@objc public var minValue: Double = 0.0 {
didSet {
sliderObject.slider.scaleMinValue = minValue
}
}

@objc public var maxValue: Double = 1.0 {
didSet {
sliderObject.slider.scaleMaxValue = maxValue
}
}

@objc public var leftKnobValue: Double = 0.0 {
didSet {
sliderObject.slider.changeLeftKnob(value: leftKnobValue)
sliderObject.slider.defaultValueLeftKnob = leftKnobValue
}
}

@objc public var rightKnobValue: Double = 1.0 {
didSet {
sliderObject.slider.changeRightKnob(value: rightKnobValue)
sliderObject.slider.defaultValueRightKnob = rightKnobValue
}
}

@objc public var step: Int = 0 {
didSet {
sliderObject.slider.stepIncrement = CGFloat(step)
}
}
}

So here we forward all the props and the delegate to the slider instance thanks to Swift's didSet property observer. We also set the frame of the slider, so that it can be resized if slider's parent changes its bounds.

tip

Visit Swift's docs to learn about didSet property observer

Good, now let's use this view inside view manager and Fabric component view.

Complete RangeSliderView.swift file
import RangeUISlider
import UIKit

class RangeUISliderObject {
public weak var delegate: RangeSliderViewDelegate? = nil

public var slider: RangeUISlider = {
let rangeSlider = RangeUISlider()
rangeSlider.barCorners = 5
rangeSlider.barHeight = 10
rangeSlider.leftKnobColor = UIColor.systemBlue
rangeSlider.leftKnobCorners = 10
rangeSlider.leftKnobHeight = 20
rangeSlider.leftKnobWidth = 20
rangeSlider.rightKnobColor = UIColor.systemBlue
rangeSlider.rightKnobCorners = 10
rangeSlider.rightKnobHeight = 20
rangeSlider.rightKnobWidth = 20
rangeSlider.showKnobsLabels = true
return rangeSlider
}()

private var isDragging: Bool = false

init() {
self.slider.delegate = self
}
}

extension RangeUISliderObject: RangeUISliderDelegate {
public func rangeIsChanging(minValueSelected: CGFloat, maxValueSelected: CGFloat, slider: RangeUISlider) {
self.delegate?.sendOnRangeSliderViewValueChangeEvent(minValue: minValueSelected, maxValue: maxValueSelected)
}

public func rangeChangeFinished(minValueSelected: CGFloat, maxValueSelected: CGFloat, slider: RangeUISlider) {
if !isDragging {
return
}
self.delegate?.sendOnRangeSliderViewEndDragEvent(minValue: minValueSelected, maxValue: maxValueSelected)
self.isDragging = false
}

public func rangeChangeStarted() {
self.isDragging = true
self.delegate?.sendOnRangeSliderViewBeginDragEvent()
}
}

@objc(RangeSliderViewDelegate)
public protocol RangeSliderViewDelegate {
func sendOnRangeSliderViewBeginDragEvent()
func sendOnRangeSliderViewEndDragEvent(minValue: Double, maxValue: Double)
func sendOnRangeSliderViewValueChangeEvent(minValue: Double, maxValue: Double)
}

@objc(RangeSliderView)
public class RangeSliderView: UIView {
@objc public weak var delegate: RangeSliderViewDelegate? = nil {
didSet {
sliderObject.delegate = delegate
}
}

@objc public var onRangeSliderViewBeginDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewEndDrag: ((NSDictionary?) -> Void)? = nil
@objc public var onRangeSliderViewValueChange: ((NSDictionary?) -> Void)? = nil

private var sliderObject = RangeUISliderObject()

public override init(frame: CGRect) {
super.init(frame: frame)
self.sliderObject.delegate = self.delegate
self.addSubview(self.sliderObject.slider)

self.sliderObject.slider.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.sliderObject.slider.topAnchor.constraint(equalTo: self.topAnchor),
self.sliderObject.slider.leadingAnchor.constraint(equalTo: self.leadingAnchor),
self.sliderObject.slider.trailingAnchor.constraint(equalTo: self.trailingAnchor),
self.sliderObject.slider.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

@objc public var activeColor: UIColor = UIColor.systemBlue {
didSet {
sliderObject.slider.rangeSelectedColor = activeColor
}
}

@objc public var inactiveColor: UIColor = UIColor.systemGray {
didSet {
sliderObject.slider.rangeNotSelectedColor = inactiveColor
}
}

@objc public var minValue: Double = 0.0 {
didSet {
sliderObject.slider.scaleMinValue = minValue
}
}

@objc public var maxValue: Double = 1.0 {
didSet {
sliderObject.slider.scaleMaxValue = maxValue
}
}

@objc public var leftKnobValue: Double = 0.0 {
didSet {
sliderObject.slider.changeLeftKnob(value: leftKnobValue)
sliderObject.slider.defaultValueLeftKnob = leftKnobValue
}
}

@objc public var rightKnobValue: Double = 1.0 {
didSet {
sliderObject.slider.changeRightKnob(value: rightKnobValue)
sliderObject.slider.defaultValueRightKnob = rightKnobValue
}
}

@objc public var step: Int = 0 {
didSet {
sliderObject.slider.stepIncrement = CGFloat(step)
}
}
}

RangeSliderViewManager.h

As usual the view manager header will be simple:

ios/RangeSliderViewManager.h
#import <React/RCTUIManager.h>
#import <React/RCTViewManager.h>

@class RangeSliderView;

@interface RangeSliderViewManager : RCTViewManager

@end

We declare the view manager class that extends RCTViewManager.

One thing you may have noticed is RCTUIManager import - we will use it to implement native commands for the old architecture view.

The RangeSliderView Swift class has its "forward-declaration" (check out Apple's Swift-ObjC interop dedicated docs section).

RangeSliderViewManager.mm

ios/RangeSliderViewManager.mm
#import "RangeSliderViewManager.h"

#import "RangeSliderPackage-Swift.h"

#if RCT_NEW_ARCH_ENABLED
#else
@interface RangeSliderViewManager () <RangeSliderViewDelegate>
@end
#endif

@implementation RangeSliderViewManager {
RangeSliderView *sliderView;
}

RCT_EXPORT_MODULE(RangeSliderView)

RCT_EXPORT_VIEW_PROPERTY(activeColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(inactiveColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(leftKnobValue, double)
RCT_EXPORT_VIEW_PROPERTY(minValue, double)
RCT_EXPORT_VIEW_PROPERTY(maxValue, double)
RCT_EXPORT_VIEW_PROPERTY(rightKnobValue, double)
RCT_EXPORT_VIEW_PROPERTY(step, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onRangeSliderViewBeginDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRangeSliderViewEndDrag, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRangeSliderViewValueChange, RCTDirectEventBlock)

#if RCT_NEW_ARCH_ENABLED
#else
RCT_EXPORT_METHOD(setLeftKnobValueProgrammatically:(nonnull NSNumber*) reactTag value:(NSInteger) value) {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RangeSliderView class]]) {
return;
}
[(RangeSliderView *)view setLeftKnobValue:value];
}];
}

RCT_EXPORT_METHOD(setRightKnobValueProgrammatically:(nonnull NSNumber*) reactTag value:(NSInteger) value) {
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view || ![view isKindOfClass:[RangeSliderView class]]) {
return;
}
[(RangeSliderView *) view setRightKnobValue:value];
}];
}

- (void)sendOnRangeSliderViewValueChangeEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (sliderView.onRangeSliderViewValueChange) {
sliderView.onRangeSliderViewValueChange(@{ @"leftKnobValue": @(minValue), @"rightKnobValue": @(maxValue) });
}
}

- (void)sendOnRangeSliderViewBeginDragEvent
{
if (sliderView.onRangeSliderViewBeginDrag) {
sliderView.onRangeSliderViewBeginDrag(nil);
}
}

- (void)sendOnRangeSliderViewEndDragEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (sliderView.onRangeSliderViewEndDrag) {
sliderView.onRangeSliderViewEndDrag(@{ @"leftKnobValue": @(minValue), @"rightKnobValue": @(maxValue) });
}
}

- (UIView *)view
{
RangeSliderView *view = [RangeSliderView new];
view.delegate = self;
sliderView = view;
return view;
}
#endif

@end

And as for every view manager class, we start with RCT_EXPORT_MODULE macro and we declare exported properties with RCT_EXPORT_VIEW_PROPERTY macro.

For the old architecture mode, we have to do 2 additional things:

  • declaring native commands (using RCT_EXPORT_METHOD macro and RCTUIManager class)
  • handling events emitting (with RCTDirectEventBlock props and RangeSliderViewDelegate methods)

The view getter is also declared for the old arch - for new arch we are just using Fabric component view.

RangeSliderViewComponentView.h

ios/RangeSliderViewComponentView.h
#if RCT_NEW_ARCH_ENABLED
#import <React/RCTViewComponentView.h>

@class RangeSliderView;

@interface RangeSliderViewComponentView : RCTViewComponentView

@end

#endif

Here the Fabric component view extends base RCTViewComponentView class.

The RangeSliderView Swift class has its "forward-declaration" (check out Apple's Swift-ObjC interop dedicated docs section).

RangeSliderViewComponentView.mm

The implementation for the Fabric component will be quite large, so let's try to break it into parts.

The first part - boilerplate:

ios/RangeSliderViewComponentView.mm
#if RCT_NEW_ARCH_ENABLED
#import "RangeSliderViewComponentView.h"

#import <React/RCTConversions.h>

#import <react/renderer/components/RangeSliderPackage/ComponentDescriptors.h>
#import <react/renderer/components/RangeSliderPackage/EventEmitters.h>
#import <react/renderer/components/RangeSliderPackage/Props.h>
#import <react/renderer/components/RangeSliderPackage/RCTComponentViewHelpers.h>

#import "RCTFabricComponentsPlugins.h"

using namespace facebook::react;

@interface RangeSliderViewComponentView () <RCTRangeSliderViewViewProtocol>
@end

@implementation RangeSliderViewComponentView

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RangeSliderViewProps>();
_props = defaultProps;
}

return self;
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(props);

[super updateProps:props oldProps:oldProps];
}

- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
// That component does not accept child views
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
// That component does not accept child views
}

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTRangeSliderViewHandleCommand(self, commandName, args);
}

- (void)setLeftKnobValueProgrammatically:(double)value
{
//
}

- (void)setRightKnobValueProgrammatically:(double)value
{
//
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RangeSliderViewComponentDescriptor>();
}

@end

Class<RCTComponentViewProtocol> RangeSliderViewCls(void)
{
return RangeSliderViewComponentView.class;
}
#endif

Component begins with some new arch imports and conversion helpers.

We make the component extending code-generated protocol - this protocol has all the native commands methods that we declared in JS spec.

Next we implement all required methods and we create RangeSliderViewCls function.

If you take a closer look, we override to methods related to the child components (- mountChildComponentView:index: and - unmountChildComponentView:index:). Those methods can be used to control how the child views should be added/removed in the Fabric component. In our case, we prevent adding/removal to be sure that our slider view does not have any child views.

Another interesting thing takes place in - handleCommand:args: method - it invokes RCTRangeSliderViewHandleCommand function. That function is code-generated as well as RCTRangeSliderViewViewProtocol protocol and is used to forward native commands calls, to dedicated methods (in our case: - setLeftKnobValueProgrammatically & - setRightKnobValueProgrammatically).

OK - second part, let's start filling the boilerplate with our slider view:

ios/RangeSliderViewComponentView.mm
// ...

#import "RangeSliderPackage-Swift.h"

using namespace facebook::react;

@interface RangeSliderViewComponentView () <RCTRangeSliderViewViewProtocol>
@end

@interface RangeSliderViewComponentView () <RangeSliderViewDelegate>
@end

@implementation RangeSliderViewComponentView

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RangeSliderViewProps>();
_props = defaultProps;

RangeSliderView *view = [RangeSliderView new];
view.delegate = self;

self.contentView = (UIView *)view;
}

return self;
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(props);

RangeSliderView *view = (RangeSliderView *)self.contentView;

if (oldViewProps.activeColor != newViewProps.activeColor) {
[view setActiveColor:RCTUIColorFromSharedColor(newViewProps.activeColor)];
}

if (oldViewProps.inactiveColor != newViewProps.inactiveColor) {
[view setInactiveColor:RCTUIColorFromSharedColor(newViewProps.inactiveColor)];
}

if (oldViewProps.step != newViewProps.step) {
[view setStep:newViewProps.step];
}

if (oldViewProps.minValue != newViewProps.minValue) {
[view setMinValue:newViewProps.minValue];
}

if (oldViewProps.maxValue != newViewProps.maxValue) {
[view setMaxValue:newViewProps.maxValue];
}

if (oldViewProps.leftKnobValue != newViewProps.leftKnobValue) {
[view setLeftKnobValue:newViewProps.leftKnobValue];
}

if (oldViewProps.rightKnobValue != newViewProps.rightKnobValue) {
[view setRightKnobValue:newViewProps.rightKnobValue];
}

[super updateProps:props oldProps:oldProps];
}

// ...

@end

// ...

What's going on here?

  • RangeSliderView is imported via RangeSliderPackage-Swift.h import
  • RangeSliderViewDelegate is implemented by the Fabric component
  • RangeSliderView is initialized and set as a contentView, its delegate is set to this Fabric component's instance
  • all props are handled in - updateProps:oldProps:

But that's still only half of the things - XCode is probably warning you that RangeSliderViewDelegate is not implemented - let's fix it!

ios/RangeSliderViewComponentView.mm
// ...

@implementation RangeSliderViewComponentView

// ...

- (void)sendOnRangeSliderViewValueChangeEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewValueChange(
RangeSliderViewEventEmitter::OnRangeSliderViewValueChange{
.leftKnobValue = minValue,
.rightKnobValue = maxValue
});
}
}

- (void)sendOnRangeSliderViewBeginDragEvent
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewBeginDrag(
RangeSliderViewEventEmitter::OnRangeSliderViewBeginDrag{});
}
}

- (void)sendOnRangeSliderViewEndDragEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewEndDrag(
RangeSliderViewEventEmitter::OnRangeSliderViewEndDrag{
.leftKnobValue = minValue,
.rightKnobValue = maxValue
});
}
}

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTRangeSliderViewHandleCommand(self, commandName, args);
}

- (void)setLeftKnobValueProgrammatically:(double)value
{
RangeSliderView *view = (RangeSliderView *)self.contentView;
[view setLeftKnobValue:value];
}

- (void)setRightKnobValueProgrammatically:(double)value
{
RangeSliderView *view = (RangeSliderView *)self.contentView;
[view setRightKnobValue:value];
}

// ...

@end

// ...

So now all warnings should be cleared - delegate methods are implemented and we also handled native commands.

info

Event emitters in new architecture mode are done in C++ and are code-generated

Complete RangeSliderViewComponentView.mm file
#if RCT_NEW_ARCH_ENABLED
#import "RangeSliderViewComponentView.h"

#import <React/RCTConversions.h>

#import <react/renderer/components/RangeSliderPackage/ComponentDescriptors.h>
#import <react/renderer/components/RangeSliderPackage/EventEmitters.h>
#import <react/renderer/components/RangeSliderPackage/Props.h>
#import <react/renderer/components/RangeSliderPackage/RCTComponentViewHelpers.h>

#import "RCTFabricComponentsPlugins.h"

#import "RangeSliderPackage-Swift.h"

using namespace facebook::react;

@interface RangeSliderViewComponentView () <RCTRangeSliderViewViewProtocol>
@end

@interface RangeSliderViewComponentView () <RangeSliderViewDelegate>
@end

@implementation RangeSliderViewComponentView

- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
static const auto defaultProps = std::make_shared<const RangeSliderViewProps>();
_props = defaultProps;

RangeSliderView *view = [RangeSliderView new];
view.delegate = self;

self.contentView = (UIView *)view;
}

return self;
}

- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
{
const auto &oldViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(_props);
const auto &newViewProps = *std::static_pointer_cast<const RangeSliderViewProps>(props);

RangeSliderView *view = (RangeSliderView *)self.contentView;

if (oldViewProps.activeColor != newViewProps.activeColor) {
[view setActiveColor:RCTUIColorFromSharedColor(newViewProps.activeColor)];
}

if (oldViewProps.inactiveColor != newViewProps.inactiveColor) {
[view setInactiveColor:RCTUIColorFromSharedColor(newViewProps.inactiveColor)];
}

if (oldViewProps.step != newViewProps.step) {
[view setStep:newViewProps.step];
}

if (oldViewProps.minValue != newViewProps.minValue) {
[view setMinValue:newViewProps.minValue];
}

if (oldViewProps.maxValue != newViewProps.maxValue) {
[view setMaxValue:newViewProps.maxValue];
}

if (oldViewProps.leftKnobValue != newViewProps.leftKnobValue) {
[view setLeftKnobValue:newViewProps.leftKnobValue];
}

if (oldViewProps.rightKnobValue != newViewProps.rightKnobValue) {
[view setRightKnobValue:newViewProps.rightKnobValue];
}

[super updateProps:props oldProps:oldProps];
}

- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
// That component does not accept child views
}

- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
{
// That component does not accept child views
}

- (void)sendOnRangeSliderViewValueChangeEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewValueChange(
RangeSliderViewEventEmitter::OnRangeSliderViewValueChange{
.leftKnobValue = minValue,
.rightKnobValue = maxValue
});
}
}

- (void)sendOnRangeSliderViewBeginDragEvent
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewBeginDrag(
RangeSliderViewEventEmitter::OnRangeSliderViewBeginDrag{});
}
}

- (void)sendOnRangeSliderViewEndDragEventWithMinValue:(double)minValue maxValue:(double)maxValue
{
if (_eventEmitter != nil) {
std::dynamic_pointer_cast<const RangeSliderViewEventEmitter>(_eventEmitter)
->onRangeSliderViewEndDrag(
RangeSliderViewEventEmitter::OnRangeSliderViewEndDrag{
.leftKnobValue = minValue,
.rightKnobValue = maxValue
});
}
}

- (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
{
RCTRangeSliderViewHandleCommand(self, commandName, args);
}

- (void)setLeftKnobValueProgrammatically:(double)value
{
RangeSliderView *view = (RangeSliderView *)self.contentView;
[view setLeftKnobValue:value];
}

- (void)setRightKnobValueProgrammatically:(double)value
{
RangeSliderView *view = (RangeSliderView *)self.contentView;
[view setRightKnobValue:value];
}

+ (ComponentDescriptorProvider)componentDescriptorProvider
{
return concreteComponentDescriptorProvider<RangeSliderViewComponentDescriptor>();
}

@end

Class<RCTComponentViewProtocol> RangeSliderViewCls(void)
{
return RangeSliderViewComponentView.class;
}
#endif

You can check training repo for Objective-C & Swift implementation here and Objective-C-only implementation here.

That's iOS part, now let's go to Android!