Mixing UIKit and SwiftUI

Cory D. Wiles
5 min readSep 29, 2019

--

Like most iOS / MacOS developers, I saw SwiftUI as one of the biggest announcements since Swift itself back in 2014. It allows a developer to move away from Storyboards / IB, as well as, write UI code that isn’t weighted with step-by-step directions on how the UI should display on the device.

I personally stopped using IB after 6 months of doing iOS development 10 years ago. I never liked, nor used Storyboards for any of my personal projects over the years as well. I wasn’t dismissive right away. I did try them out, they seemed very cumbersome and collaboration was always a PITA.

UIKit vs SwiftUI

During the week(s) after the announcement I read a lot of Tweets, comments and articles addressing: What is better, UIKit or SwiftUI? or “I’m a new iOS developer, should I bother learning UIKit or jump right into SwiftUI?”. You don’t have a binary choice. As a matter of fact it isn’t a true or false question, rather a multiple choice.

The answer is both.

UIKit has powered iOS since day 1. It is extremely powerful and what every iOS app has used for the past 10 years. That’s a lot of UIKit code Much like Swift itself, the SwiftUI adoption will come quickly. The ease of use and what you can accomplish with little code makes it a no brainer.

Even with the efficency and low barrier to entry to using SwiftUI there are limitations that only UIKit can overcome. If you are trying to build complex layouts or custom components then UIKit has you covered, but there is plenty of meat on the bone for what SwiftUI can do for you.

Beyond ContentView_Previews and Demos

“Damn, that’s really cool!” “You can do that with only how many lines?!”

These were literal reactions when I watched the sessions, demos and examples that Apple and other developers published, but I wondered how quickly could I use SwiftUI in my own apps.

Challenge Accepted

While working on Significant Other this summer I dropped iOS 12 support (because I can cool stuff like that). Without having to support a legacy OS I freed myself to see if (how) SwiftUI would play well with UIKit. I wasn’t going to replace all the UIKit code because a) no feasible b) this is the first release of SwiftUI which means changes are coming ( remember Swift 1–3?).

The settings screen within Significant Other is a static vertical scrolling CollectionView. The cells are pretty homogeneous. Bold title, short description and right justified button with a border and rounded corners.

The other two cell types are the avatar and the “standard” cell.

First Approach (spoiler, it didn’t work)

Nothing in this world is perfect and that includes SwiftUI. As I mentioned earlier it has its limitations. One of which is changing the color of the line separator in a list. Shocking I know. Since I couldn’t change the color of the separator I couldn’t use a List. Naviely I thought I could just use a ScrollView, a little creativity and be done. I was wrong. I was going to have to rethink my approach which could have included abandoning SwiftUI until I later date.

Second Apprach (spoiler, it worked)

Due to the lack of basic customization that I needed with a List view my next approach was to use a ScrollView and with a custom Rectangle as the divider.

struct RowDivider: View { // MARK: Internal (properties) 
var body: some View {
Rectangle()
.fill(Color.white)
.frame(height: 1)
.padding(.leading, SettingsUIView.offset)
.padding(.trailing, SettingsUIView.offset)
}
}

Awesome…now I’m off to the races, EXCEPT I got a compile error. I checked, rechecked and checked again my code. Everything looked correct. A ScrollView has a limit of 10 subviews (technically all containers do).

Group to the rescue.

All containers in SwiftUI must return no more than ten children, which is usually fine for most purposes. However, if you need to have more than 10 views, if you need to return more than one view from your body property, or if you need to return several different kinds of view, you should use a group like this:

Source: https://www.hackingwithswift.com/quick-start/swiftui/swiftui-tips-and-tricks

Passing Data

Now that the UI is where I wanted it to be I needed to figure out how to pass data between the various SwiftUI views, as well as, the hosting view controller: SettingsViewController, specifically:

  1. Handle Button actions for the different "cells"
  2. Handle the avatar view being tapped
  3. Updating the setting’s avatar view
  4. Posting back to the current user’s record in CloudKit
  5. Updating the local cached instance of the image

Traditional patterns don’t really apply in these scenarios. Enter ObservableObject and Published from the [Combine](https://developer.apple.com/documentation/combine) framework. While detail explainations for these are for another tutorial, they were the tools that provide the modern day solution to handling UI, state and data changes in your app.

In order to communicate back to the SettingsViewController that a particular button in SettingsUIView had been touched I use a PassthroughSubject.

SettingsUIView

let subject = PassthroughSubject<SettingsDetailViewControllerType, Never>()
.
.
.
Group {
SettingsAvatarView(userManager: userManager, action: {
self.subject.send(.avatar)
})
}
Group {
RowDivider()
SettingsInfoView(title: "Share",
subtitle: "Tell others about SO")
Spacer()
SettingsButtonStackView(title: "Share", action: {
self.subject.send(.share)
})
Spacer()
}

SettingsViewController

private let settingsDetailTypeSubscriber: SettingsDetailViewControllerTypeSubscriber = SettingsDetailViewControllerTypeSubscriber()
.
.
.
self.settingsUIView.subject.subscribe(self.settingsDetailTypeSubscriber)
.
.
.
settingsUIView.subject
.sink(
receiveCompletion: { completion in
print("Received completion (sink)", completion)
},
receiveValue: { value in
print("Received value (sink)", value)

switch value {

case .avatar:
self.presentionCameraViewController()
break
case .support:
self.sendEmail()
break
case .advanced:

self.pushTo(.advanced)
break
case .subscriptions:
self.pushTo(.subscriptions)
break
case .iap:
.
.
.
).store(in: &self.subscriptions)

That handles the “callbacks” when a user taps a button or the avatar image view, but how do I update the image for the avatar image view, as well as, any other view(s) that display my profile image?

Enter @Published and @ObservedObject.

In my UserManager class I conform to ObservableObject and mark the currentUserProfileImage property as @Published:

public final class SOUserManager: UserManager, ObservableObject{
@Published public private (set) var currentUserProfileImage: UIImage = SOUser.localProfileImage ?? UIImage(named: Constants.AppConstants.DefaultProfileImage)!
}

Whenever the the property is updated any other class / struct properties that are observing the UserManager class will automagically get the updated property.

cloudkit.save(currentUser.record, callback: {saveResult inswitch saveResult {

case .success(_):

self.currentUserProfileImage = profileImage
SOUser.saveProfile(profileImage)
callback(true)
break
case .failure(.general(_)):
callback(false)
break
case .failure(.emptyresult(_)):
callback(false)
break
}
})

SettingsAvatarView

struct SettingsAvatarView: View {

// MARK: Internal (properties)

@ObservedObject var userManager: SOUserManager

var action: (() -> Void) = {}

var body: some View {

HStack {
Text("Upload Avatar").foregroundColor(.white).font(.custom("Avenir-Heavy", size: 15.0))
Spacer()
Image(uiImage: self.userManager.currentUserProfileImage)
.resizable().frame(width: 40.0, height: 40.0)
.aspectRatio(contentMode: .fill)
.clipShape(Circle())
.shadow(radius: 10)
.foregroundColor(.white)
}
.padding(SettingsUIView.offset)
.onTapGesture {
self.action()
}
}
}

Final Thoughts

SwiftUI has been amazing to work with despite its infanacy. It reminds me of the early days of Swift. Especially version 1–3. Lots of changes, headaches, but it also ushered in new areas of development. I feel the same way about SwiftUI. Digging in early to the API and developing best practices from day 1 will pay off huge in the next few years.

I’m taking the same approach I did with Swift. I would write some UI elements and utilities in Swift that I knew would work well with the other 99% of the Objective-C code. As the language matured and my confidence in using it increased I went Swift first. I have a feeling it will be “SwiftUI / Combine First” sooner rather than later.

--

--

Cory D. Wiles
Cory D. Wiles

Written by Cory D. Wiles

I code stuff in Swift. I also raise children, workout, and make a perfect old fashioned.

No responses yet