Captain’s Log: Upgrading a UIKit app to SwiftUI
With the introduction of iOS 14, SwiftUI has gained the center stage at Apple. But how does it feel to port an existing app to SwiftUI? Luckily I have just the right UIKit based app that I can port to SwiftUI. Here’s the captain’s log documenting the upgrade.
Day 0: Scenes not Windows:
My app uses AppDelegate’s Window based approach whereas iOS 13 introduced an one-app-multiple-scenes paradigm. The benefits are huge, and I can now run different scenes of the same app without worrying about state. But how do we migrate an AppDelegate based app to SceneDelegate one? Turns out it’s a three step process1:
-
Create a new SceneDelegate class that implements
UIWIndowSceneDelegate
methods. The real stuff goes into the methodfunc scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
I simply copied one from a newly created Xcode project, and added my newly created
ContentView
as the starting point. -
Add the
UIApplicationSceneManifest
to the Info.plist -
Add the 2 new functions to AppDelegate to complete the setup:
application(_:configurationForConnecting:options:)
application(_:didDiscardSceneSessions:)
Day 1: Views as first-class citizens
While storyboards (and ViewControllers) in UIKit act as a starting point, in SwiftUI Views play that role. As someone used to UIKit Storyboards, the declarative UI of SwiftUI was bit foreign. However my forays into Android, Xamarin etc have prepared me well for this moment. I didn’t face much challenge in getting the UI converted to SwiftUI components, and the icing on the cake was the cool new canvas that shows instant UI updates. Flutter devs rave about the fast feedback loops they’ve and now iDevelopers can finally have a reply. I had the following UIKit components that I replaced with SwiftUI ones, but technically SwiftUI reuses a lot of UIKit components:
- UISwitch with Toggle
- UIImageView with Image (and later a custom AsyncImage)
- UITableView with List
However the most fun part was the discovery of 2-way-bindings using @ObservedObject
property. The reduction of delegate/KVO boilerplate was also a welcome change and views get updates seamlessly. Combine has brought iOS/macOS development on par with rest of the other platforms.
Day 3: Hello ViewModels:
With the deprecation of ViewControllers in SwiftUI, the app architecture now simply replaces it with any class that can act as a ViewModel. The only thing we need to bind it to the view is to conform to the ObservableObject
type. As luck would have it, my original design was done in MVVM pattern (M-VC-VM pattern TBH) which was at odds with the pure MVVM model of SwiftUI. Luckily protocol oriented design and the loose decoupling made it surprisingly easy to extend the ViewModel to SwiftUI. The @Published
properties make it easy to connect the VM to the View with minimum efforts.
Day 4: Refactoring the networking layer
SwiftUI also introduced Combine which brought a native way to do functional reactive programming to iOS. While it’s similar in sprit to RxSwift, there’s still a learning curve to it. I was once again lucky here as my original app used RxSwift to fetch data asynchronously from the Network. The network clients now return an AnyResponse<Response,Error> intead of RxSwift’s Observable. RxSwift provides a nice way to return error using Observable.error which I replaced with Combine’s Fail(error:).eraseToAnyPublisher(). Finally at the end of the chain in the VM, I replaced RxSwift’s .subscribe(onNext:onError:onCompleted) with Combine’s .sink(receiveCompletion: receiveValue) operation. Apple also readily provides a URLSession.dataTaskPublisher(for: url) to fetch data in a reactive way2.
Day 5: Miscelleneaous:
The code I wrote to abstract and test CLLocationManager
worked without any modifiations. The unit tests were surprisingly functional with minimal tweaks. With SwiftPM becoming a firstclass citizen in Xcode, I replaced Carthage with it. Finally I also replaced Kingfisher and implemented a simple Image cache using this handy guide3.
With this migration I’m now confident that SwiftUI+Combine is the way forward for iOS development. However there are some minor glitches and shortcomings in SwiftUI that Apple is still ironing out. The great thing about SwiftUI+Combine is that it can nicely coexist with UIKit based apps and we could use the strangler pattern to refactor components.
The final refactored project is available here: https://github.com/samkhawase/walkabout
-
https://learnappmaking.com/scene-delegate-app-delegate-xcode-11-ios-13/ ↩︎
-
Apple Documentation about URLSession + Combine ↩︎
-
A neat Combine approach to cache images: ↩︎