WildCat's Blog

Rethinking SwiftUI

SwiftUI has been generally available for almost one year since last summer. After some exploration, I would like to write some thoughts about SwiftUI.

Is SwiftUI production ready?

Unfortunately, my answer of this question should be “yes and no”, just a boring answer. But please read for the details below.

As for a large part of iOS / macOS native features such as navigation and split view, SwiftUI is fairly far from production ready. In order to use these native features with SwiftUI without UIKit and AppKit, you need to work around the limitation or even wait for next major version of iOS and macOS. That can be disappointing. For example, you cannot put NavigationLink in navigationBarItems of a NavigationView. It causes app crash: https://stackoverflow.com/questions/58404725/.

However, this should not be the reason preventing you from learning and using SwiftUI in development or even production.

Firstly, we can use both UIKit and SwiftUI simultaneously in a project. We only need to do some extra work bridging these two UI frameworks. Thankfully, it should not be a difficult task as long as we can use Combine. I will talk more about this topic later in this article.

Secondly, declarative UI programming is rising dramatically. We can find that UI frameworks such as React, Vue and Flutter are gaining popularity across the community. Building UIs declaratively is an effective way and help us to think more about reusability, breaking large or fat components into small ones. The mindset of declarative programming can help us to write better code with UIKit and AppKit. In addition to client-side programming, DevOps on the sever side has been modernized a lot with Kubernetes and Docker. With these new technologies, building a large cluster on the cloud can be incredibly simple compared with the old ways.

Thirdly, the Swift features leveraged by SwiftUI can also make us to think more about improving the existing codebase. For example, we can think about getting rid of lengthy code with Property Wrapper. I would not say Apple’s usage of Swift language features is the optimal way. But it should be an illustration of best practices.

For more thoughts in a more creative manner, I highly encourage you to take a look at “Bridging the gap” by @Meng To and @John Sundell. It contains many interesting discussions and points of views for SwiftUI.

Integration with UIKit and Combine

Inevitably, we should still use UIKit or AppKit for some parts to avoid user experience loss. We can easily using UIKit inside SwiftUI with UIViewRepresentable and UIViewControllerRepresentable. We could have less difficulty while going on this direction.

However, it is more common to use SwiftUI views in our existing UIKit or AppKit codebase. The APIs of UIHostingController is quite constricted. For example, updating values for and receiving events from SwiftUI in UIKit is not very easy and direct. Luckily, we can achieve this goal by Combine and optionally EnvironmentValues. Here is an example about dismissing the UIHostingController in an action from a SwiftUI view.

To begin with, we need to extend EnvironmentValues (or we can just use HostingIdentifer as a field of a SwiftUI view):

struct HostingIdentifer {
  /// A sample publisher (subject) for communication between UIKit and SwiftUI.
  let dismissSubject = PassthroughSubject<Bool, Never>()

  func dismiss(animated: Bool) {
    dismissSubject.send(animated)
  }
}

// Using EnvironmentKey and EnvironmentValues is not required.
struct HostingIdentifierKey: EnvironmentKey {
  typealias Value = HostingIdentifer

  static let defaultValue = HostingIdentifer()
}

extension EnvironmentValues {
  var hostingIdentifier: HostingIdentifer {
    get {
      return self[HostingIdentifierKey.self]
    }
    set {
      return self[HostingIdentifierKey.self] = newValue
    }
  }
}

Then, we can define a sample SwiftUI view:

struct SampleView: View {
  @Environment(\.hostingIdentifier) var hostingIdentifier

  // Body

  /// The action to dismiss the whole hosting controller
  private func dismissSelf() {
    hostingIdentifier.dismiss(animated: false)
  }
}

After that, in the parent view controller of a UIHostingController, we can declare a HostingIdentifer and pass it into our sample view:

// In a setup method, for example, `viewDidLoad()`
let hostingIdentifier = HostingIdentifer()
let hostingVC = UIHostingController(rootView:
  rootView.environment(\.hostingIdentifier, hostingIdentifier)
)

We need to subscribe the publisher in hostingIdentifier:

// In a setup method, for example, `viewDidLoad()`
hostingIdentifier.dismissSubject.sink { [weak self] animated in
  guard let `self` = self else {
    return
  }
  self.dismiss(animated: animated, completion: nil)
}
.store(in: &subscriptions)

Alternatively, we can use the traditional NotificationCenter to deliver messages. But Combine should be a precise and light-weight approach. Notwithstanding some boilerplate needed at the very beginning, it will soon become very clear and useful.

Summary

SwiftUI demonstrates Apple’s ambition in this revolution of declarative UI programming. The method is very creative by leveraging both the new idea and existing Swift features such as generics. SwiftUI is definitely a promising framework. Although it is not mature yet, learning SwiftUI can always be helpful. Because it is a solution which can better serve both users and developers. Nevertheless, we should not compromise user experience while using SwiftUI. At the moment, we still need to use UIKit, Combine and SwiftUI together for most cases.

2020_rethinking_swiftui_poster

If thinking out of the box, we can get inspirations from Steve Jobs:

You’ve got to start with the customer experience and work backwards to the technology.
You can’t start with the technology and try to figure out where you’re going to sell it.