Select Page

Earlier this week, I showed you how to create a custom control using a bitmask. But it’s time to take it one step further by adding RxSwift to the mix. In this tutorial, we make the custom control we built reactive using RxSwift.

Cloning the Repository

Head over to GitHub and clone the project if you’d like to follow along. Open Terminal and execute the following command.

git clone [email protected]:bartjacobs/HowToCreateACustomControlUsingABitmask.git

Adding Dependencies

You can add RxSwift one of several ways. I prefer CocoaPods. Navigate to the root of the project and execute the following command to set up CocoaPods for the project.

pod init

Open the project’s Podfile in your favorite text editor and add RxSwift as a dependency.

target 'Schedules' do
  use_frameworks!
  platform :ios, '10.0'

  pod 'RxSwift'
end

From the command line, execute pod repo update to make sure the specs repositories on your machine are up to date. To install the dependencies, execute pod install.

pod repo update
pod install

This tutorial uses version 3.4.1 of RxSwift. Open the workspace CocoaPods has created for you and trigger a build by choosing Build from the Product menu. You shouldn’t see any errors or warnings.

Variables and Observable Sequences

The technique I outline in this tutorial is a technique I picked up from Krunoslav Zaher, the creator of RxSwift. Open SchedulePicker.swift and add an import statement for RxSwift.

import UIKit
import RxSwift

struct Schedule: OptionSet {

    ...
    
}

Locate the schedule property we defined in the previous tutorial.

class SchedulePicker: UIControl {

    // MARK: - Properties

    var schedule: Schedule = [] {
        didSet { updateView() }
    }

    ...
    
}

We’re going to replace the schedule property with an observable sequence, making the SchedulePicker class reactive. Subscribers can then subscribe to the observable sequence and be notified when a change occurs.

But we’re not simply going to add an observable sequence. The approach I have come to appreciate involves a private Variable and a public Observable. The Variable is used internally by the SchedulePicker class. The read-only observable sequence is exposed to the public.

This is what that looks like. The scheduleVariable property is of type Variable<Schedule> and it’s initialized with the same value the schedule property was initialized with. The schedule property is of type Observable<Schedule>. It simply returns the BehaviorSubject instance of the scheduleVariable property.

class SchedulePicker: UIControl {

    // MARK: - Properties

    private var scheduleVariable: Variable<Schedule> = Variable<Schedule>(Schedule(rawValue: 0))
    public var schedule: Observable<Schedule> { return scheduleVariable.asObservable() }

    ...

}

Under the hood, a Variable manages a BehaviorSubject. The asObservable() method provides read access to the BehaviorSubject. This is what the implementation of the asObservable() method looks like in the RxSwift source.

/// - returns: Canonical interface for push style sequence
public func asObservable() -> Observable<E> {
    return _subject
}

Refactoring the SchedulePicker Class

The compiler has generated a few errors we need to take care of. We need to refactor the SchedulePicker class to make it work with the scheduleVariable property. The changes are minor and easy to understand.

In updateButtons(), we ask scheduleVariable for its value and use the Schedule instance to update the buttons of the SchedulePicker instance.

private func updateButtons() {
    // Helpers
    let schedule = scheduleVariable.value

    buttons[0].isSelected = schedule.contains(.monday)
    buttons[1].isSelected = schedule.contains(.tuesday)
    buttons[2].isSelected = schedule.contains(.wednesday)
    buttons[3].isSelected = schedule.contains(.thursday)
    buttons[4].isSelected = schedule.contains(.friday)
    buttons[5].isSelected = schedule.contains(.saturday)
    buttons[6].isSelected = schedule.contains(.sunday)
}

In toggleSchedule(_:), we make a similar change. We ask scheduleVariable for its value and update the Schedule instance. Instead of invoking sendActions(for:), we update the value property of scheduleVariable.

// MARK: - Actions

@IBAction func toggleSchedule(_ sender: UIButton) {
    guard let index = buttons.index(of: sender) else {
        return
    }

    // Helpers
    let element: Schedule.Element
    var schedule = scheduleVariable.value

    switch index {
    case 0: element = .monday
    case 1: element = .tuesday
    case 2: element = .wednesday
    case 3: element = .thursday
    case 4: element = .friday
    case 5: element = .saturday
    default: element = .sunday
    }

    // Update Schedule
    if schedule.contains(element) {
        schedule.remove(element)
    } else {
        schedule.insert(element)
    }

    // Update Buttons
    updateButtons()

    // Update Schedule Variable
    scheduleVariable.value = schedule
}

Under the hood, a next event is emitted by the BehaviorSubject of the Variable. This is what the implementation of the value property looks like in the RxSwift source.

/// Gets or sets current value of variable.
///
/// Whenever a new value is set, all the observers are notified of the change.
///
/// Even if the newly set value is same as the old value, observers are still notified for change.
public var value: E {
    get {
        _lock.lock(); defer { _lock.unlock() }
        return _value
    }
    set(newValue) {
        #if DEBUG
            if AtomicIncrement(&_numberOfConcurrentCalls) > 1 {
                rxFatalError("Warning: Recursive call or synchronization error!")
            }

            defer {
                _ = AtomicDecrement(&_numberOfConcurrentCalls)
            }
        #endif
        _lock.lock()
        _value = newValue
        _lock.unlock()

        _subject.on(.next(newValue))
    }
}

The last line is of most interest to us.

_subject.on(.next(newValue))

We also need to refactor the setupButtons() method. After setting up the user interface, the SchedulePicker instance subscribes to the schedule property. When a new value is emitted, updateButtons() is invoked.

private func setupButtons() {
    ...
    
    // Subscribe to Schedule
    schedule.subscribe(onNext: { [unowned self] value in
            // Update Buttons
            self.updateButtons()
        })
        .addDisposableTo(disposeBag)
}

To satisfy the compiler, we define a constant property, disposeBag, of type DisposeBag.

// MARK: -

private let disposeBag = DisposeBag()

This also means that we can remove updateView(). We no longer need this method.

Refactoring the ViewController Class

We need to make a few changes to the ViewController class. But before we do, we need to implement a setter method to set the schedule of the SchedulePicker instance. We can no longer set the value of the schedule property.

Add the following method to the SchedulePicker class. There are other solutions, but this works fine. I prefer not to expose the scheduleVariable property to the public and this solution allows me to keep it private.

// MARK: - Public API

public func setSchedule(_ newSchedule: Schedule) {
    scheduleVariable.value = newSchedule
}

In the setupSchedulePicker() method of the ViewController class, we set the schedule using the setSchedule(_:) method we defined in the SchedulePicker class.

fileprivate func setupSchedulePicker() {
    // Fetch Stored Value
    let scheduleRawValue = UserDefaults.standard.integer(forKey: UserDefaults.Keys.schedule)

    // Configure Schedule Picker
    schedulePicker.setSchedule(Schedule(rawValue: scheduleRawValue))
}

Add an import statement for RxSwift and define a constant property, disposeBag, of type DisposeBag.

// MARK: -

private let disposeBag = DisposeBag()

Head back to the setupSchedulePicker() method and subscribe to the schedule observable sequence. When the schedule is modified, we update the value stored in the user defaults database. Remember that the synchronize() call is optional and only recommended during development.

fileprivate func setupSchedulePicker() {
    // Fetch Stored Value
    let scheduleRawValue = UserDefaults.standard.integer(forKey: UserDefaults.Keys.schedule)

    // Configure Schedule Picker
    schedulePicker.setSchedule(Schedule(rawValue: scheduleRawValue))

    // Subscribe to Schedule
    schedulePicker.schedule.subscribe(onNext: {
            // Helpers
            let userDefaults = UserDefaults.standard

            // Store Value
            userDefaults.set($0.rawValue, forKey: UserDefaults.Keys.schedule)
            userDefaults.synchronize()
        })
        .addDisposableTo(disposeBag)
}

We can now remove the scheduleDidChange(_:) action and unwire the action in Main.storyboard.

Build and Run

Build and run the application to give it a try. You shouldn’t see any warnings or errors. It’s important to note that RxCocoa uses a different approach to make UIKit components reactive. The advantage of this technique is that it’s lightweight and requires very little setup.

You can find the source files of this tutorial on GitHub. Let me know if anything’s unclear or if you have any questions about this tutorial. Leave them in the comments below or reach out to me on Twitter.