Presenting the UIAlertController with RxSwift

Presenting the UIAlertController with RxSwift

Showing an action sheet seems not to be a typical use case for the Observable. My first implementation ended with multiple PublishSubject inside UIViewController and ViewModel. Multiple subscriptions inside Observable’s chain is usually a code smell. Of course, I didn’t have multiple subscribe method calls inside my code, but only a few bindings with bindTo. However, bindTo is just a wrapper over the subscribe method. In the end, I missed the sign of the code smell.

In this article, I want to show you how you can separate presentation of UIAlertController from the UIViewController. Moreover, I want to show you how to do implement it using the RxSwift.


If I had had current knowledge these days I would definitely write unit tests for the action sheet stuff. TDD always helps in revealing code smells!

Example – the requirements

Everything is easier to understand if it is followed by the example. So, meet the requirements for our simple application.

I would like you to write an app which … changes the avatar 💯👊! The use case is simple: User can set the image from the library or choose the last image taken. We will use the action sheet to show available options to the user.


Drawing the RxDiagram

When you start thinking about RxDiagram you will end up with the conclusion what you really want is to map Observable<Void> into Observable<UIImage>:


buttonTap:     ---X---------X-------->
image:         ------I------------I-->

buttonTap
X - a button tap event
I - an image

What I want you to look closer at is that the UIImage events are moved in time according to corresponding button taps. The user has to select a source of the image which is shown on an action sheet.

Do you remember the diagram which represents API request?
The question is, what’s the difference between doing an API request and choosing an image over an action sheet? None, I would say. It means you can try to use the same Observable.create to wrap UIKit API to present a UIActionController.

Before writing any line of code I would like you to draw a more detailed diagram first 😉


buttonTap:           ----X-----------X----->
actionSheetTap:      -------T------------T->
photoAuthorization:  ----------A---------A->
imageFromPHLibrary:  ------------I-------I->


X - a button tap event
T - an action sheet option tap event
A - authorization status
I - UIImage event

In this article, I want to take care of actionSheetTap and how to wrap action sheet within the Observable.

Ready, steady, go!

First of all, we will start from writing an enum which defines the source of the image. It will help us during displaying the action sheet:

enum ImageSource: CustomStringConvertible {
  case lastPhotoTaken
  case imagePicker
  
  var description: String {
    switch self {
    case .lastPhotoTaken:
      return "Last photo taken"
    case .imagePicker:
      return "Choose image from library"
    }
  }
}

Now when we know the image sources we can create the Observable<ImageSource>. We have to wrap existing UIKit within the Observable. In 99% the Observable.create is a right tool. The same is in our case:

var image: Observable<UIImage> {
  return selectedOption
        .flatMap { source in
                switch(source):
                    case .lastPhotoTaken:
                    // return Observable with last photo taken
                    case .imagePicker:
                    // return Observable with the image from gallery
         }
}
private var selectedOption: Observable<ImageSource> {
  return Observable.create { [weak self] observer in
    guard let `self` = self else {
      observer.onCompleted()
      return Disposables.create()
    }
    let actionSheet = self.prepareActionSheet(with: observer)
    self.presenter?.present(actionSheet)
    
    return Disposables.create {
      actionSheet.dismiss(animated: true, completion: nil)
    }
  }
}

I hope the code is self-explanatory. You have to create the UIAlertController and present it using a presenter. The presenter is just a UIViewController on top of which you want to present the action sheet.

However, when you use the Observable.create you have to return the Disposable. Disposable is used to clean internal resources used by the Observable. It is a good place to dismiss the action sheet. If Observable is released/canceled, we will ensure the action sheet is dismissed.

Handling UIAlertAction behavior

Furthermore, you have to handle the actions displayed on top of UIAlertController. When a user presses any button of presented action sheet we want to pass that information to the observer. We do so, by sending a source as next event.

private func prepareActionSheet(with actionTapObserver: AnyObserver<ImageSource>) -> UIAlertController {
  let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
  prepareActionSheetActions(with: actionTapObserver)
  .forEach { actionSheet.addAction($0) }
  return actionSheet
}

private func prepareActionSheetActions(with tapObserver: AnyObserver<ImageSource>) -> [UIAlertAction] {
  var actions = createSourcesActions(with: tapObserver)
  let cancel = createCancelAction(with: tapObserver)
  actions.append(cancel)
  return actions
}

private func createSourcesActions(with tapObserver: AnyObserver<ImageSource>) -> [UIAlertAction] {
  return sources.map { source in
    return UIAlertAction(title: source.description, style: .default) { _ in
      tapObserver.onNext(source)
      tapObserver.onCompleted()
    }
  }
}

private func createCancelAction(with tapObserver: AnyObserver<ImageSource>) -> UIAlertAction {
  return UIAlertAction(title: Strings.cancel, style: .cancel) { _ in
    tapObserver.onCompleted()
  }
}

If a user touches the cancel button, you have to send completed event.

Sending completed event is very important. Don’t forget to send it whenever you know the "job" is finished. If an Observable receives completed or error event it immediately disposes itself, which helps in avoiding retain cycles.

Make it more abstract 💪

The current implementation has one thing which I don’t like. The enum. Every time you use an enum or a switch think if you can make your code more abstract. I don’t want to say it is always a good thing to make things more abstract. Sometimes the gain may not be worth the cost.

However, in our example, it is worth to replace the enum ImageSource with a protocol. Using a protocol is a key to obeying the open-close principle:

protocol ImageSource: CustomStringConvertible {
    var image: Observable<UIImage> { get }
}

Now, you want to pass the array of ImageSource into the ImageSourceChooser during the init:

init(sources: [ImageSource]) {
  self.sources = sources
}

At this moment you need to think … What do you want to send in next event to the observer? It used to be the enum ImageSource. You can still send the ImageSource or … a better solution would be to send the image property of that protocol:

private func createSourcesActions(with tapObserver: AnyObserver<Observable<UIImage>>) -> [UIAlertAction] {
    return sources.map { source in
        return UIAlertAction(title: source.description, style: .default) { _ in
            tapObserver.onNext(source.image)
            tapObserver.onCompleted()
        }
    }
}

Such a change requires also changes of types in other functions:

private var selectedOption: Observable<Observable<UIImage>> { ... }

private func prepareActionSheet(with actionTapObserver: AnyObserver<Observable<UIImage>>) -> UIAlertController { ... }

private func prepareActionSheetActions(with tapObserver: yObserver<Observable<UIImage>>) -> [UIAlertAction] { ... }

private func createCancelAction(with tapObserver: AnyObserver<Observable<UIImage>>) -> UIAlertAction { ... }

In the end, we need to change to the body of var image. Since selectedOption sends an Observable<UIImage> in every event, you can just use the Observable inside the flatMap:

var image: Observable<UIImage> {
  return selectedOption
  .flatMap { $0 }
}

The last word

I hope the above example helps you in writing more isolated features :). If you don’t know how to express something in Observable chain draw the diagram. It really helps a lot.

Observable.create is a powerful piece of code which allows you to easily create your custom observables by wrapping an existing API. It can be used to wrap a REST API request or to wrap UIKit API to display UIAlertController 🙂

You can find the code with working example on my github.

What do you think about RxSwift? Have wrapping the UIAlertController helped you? Share your thoughts in comments below!