Many SwiftUI tutorials exist on the internet these days. This is not going to be one of them! Instead, today I’d like to share with you my personal story of how someone with 5-year prior development experience in UIKit (an older UI framework for iOS) learned an entirely different concept for the first time – SwiftUI (a new UI framework for all Apple platforms).
Many hands make light work
I joined CN Group this year and was assigned to a project written in SwiftUI. It was both exciting and a little scary at the same time, as SwiftUI introduced an entirely different paradigm to iOS and all my prior UIKit knowledge was of no help. Thankfully, my new and more experienced colleagues were at hand since CN Group is an early adopter of SwiftUI.
Since its introduction in June 2019, they have developed several applications of various types in SwiftUI. For example, CN Group released an application for e-commerce, requiring pixel-perfect design and tons of animation. Then there is an IoT (Internet of Things) application building upon BLE (Bluetooth Low Energy) communication with an external smart device. And last but not least there is an AR (Augmented Reality) application for construction workers that leverages a LiDAR scanner.
Lessons learned
Since SwiftUI is entirely different from UIKit, I had to start from the very beginning. A SwiftUI Views Quick Start guide [1] was recommended to me as a great starting point - and indeed, it was. I discovered lots of useful information about basic UI controls, layout components, view sizing, view modifiers and much more. Another help was All SwiftUI property wrappers explained and compared [2] that is very handy as a great overview. Here’s an example of how to use the @EnvironmentObject property wrapper to pass values down the view hierarchy.
// Pass values down
let contentView = ParentView()
.environmentObject(theme)
.environmentObject(PartialSheetManager())
.environmentObject(DialogManager())
// Get values in any view down in hierarchy
struct AnyChildView: View {
@EnvironmentObject var theme: Theme
@EnvironmentObject var partialSheet: PartialSheetManager
…
}
When I then started to code with SwiftUI on my own, it seemed to be quite easy. With less code required, UI development was super-fast and very intuitive. There is also a live preview feature that, most of the time, eliminates the need to run the application in a simulator. As a result, implementation time is significantly reduced, and it feels almost like magic! For example, a shape in the following figure can be implemented with just 20 lines of code! Plus, it scales, and you can set any background or border you want.
struct ProgressBarWithEndBubble: Shape {
let scaleCircle: CGFloat = 0.5
let scalePath: CGFloat = 12/32
let startAngleCircle: Double = 202
let endAngleCircle: Double = 158
let startAnglePath: Double = 90
let endAnglePath: Double = 270
func path(in rect: CGRect) -> Path {
let circleRadius = rect.height * scaleCircle
let pathRadius = circleRadius * scalePath
return Path { path in
path.move(to: .init(x: pathRadius, y: rect.midY - pathRadius))
path.addArc(center: .init(x: rect.maxX - circleRadius, y: rect.midY),
radius: circleRadius,
startAngle: .init(degrees: startAngleCircle),
endAngle: .init(degrees: endAngleCircle),
clockwise: false)
path.addArc(center: .init(x: rect.minX + pathRadius, y: rect.midY),
radius: pathRadius,
startAngle: .init(degrees: startAnglePath),
endAngle: .init(degrees: endAnglePath),
clockwise: false)
}
}
}
Coming from an UIKit background, the most difficult part for me was getting used to the fact that I was no longer in control of the view hierarchy. As SwiftUI is a declarative framework, its main concept is that you define “what” should be displayed instead of “how” it should be implemented. As explained in Demystify SwiftUI [3], you still need to follow some rules in order to achieve the animations you desire. Your code is then transformed (and optimised) to the view hierarchy on your behalf. There is a downside - you will no longer get much benefit from the view hierarchy debugger.
I also struggled a bit with atomic design principles. Even though these are not directly involved in the SwiftUI framework, it helps to keep your code clean. Atomic design is a methodology that defines 5 levels of UI - atom, molecule, organism, template, screen - where each builds on lower-level components (e.g., molecules consist of atoms, etc.). In UIKit, every new UIView (the base class for viewable content) in a hierarchy could result in potential performance issue. This doesn’t apply for SwiftUI. On the contrary, you are encouraged to decompose your UI into smaller components with zero to little performance impact. This also results in improved code readability. Here is an example of such a component (two horizontally aligned buttons), that is both easy to reuse and to read:
struct AppLandingActionView: View {
let onLogIn: Callback
let onSignUp: Callback
var body: some View {
HStack(spacing: Theme.spaces.s4) {
RoundedButton(“Log In”, .quaternary, action: onLogIn)
RoundedButton(“Sign Up”, .primary, action: onSignUp)
}
}
}
Let’s be a little negative
To be honest, there is still some functionality missing from SwiftUI. This is particularly obvious if you know that something missing was available in UIKit. For example, in SwiftUI, List (equivalent to UITableView) is still quite limited, as there is no way to react to scroll events and, until recently, it was not even possible to customise separators (introduced with iOS 15 in September 2021). You also need to be cautious when it comes to GeometryReader as extensive use might result in performance issues. And even as basic a component as text inputs can give you hard times in some cases - there’s more about that in SwiftUI in production [4]. Luckily, SwiftUI and UIKit are interoperable with each other, so if you find something that really can’t be done in SwiftUI, you can always switch back to UIKit as your last option. The following figure contains such an example of using UITextField in SwiftUI, including reacting to SwiftUI environment values and using new property wrappers, which makes final usage really SwiftUI-like.
// SwiftUI wrapper for UIKit text field view
struct DynamicTextField: UIViewRepresentable {
@Binding var text: String
let placeholder: String
let textField = InsetTextField() // custom UITextField subclass
private var textInsets: EdgeInsets = .init()
private var isSecure: Bool
private var onCommit: (() -> Void)? = nil
init(text: Binding<String>, placeholder: String = "", isSecure: Bool = false) {
self._text = text
self.placeholder = placeholder
self.isSecure = isSecure
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIView(context: Context) -> InsetTextField {
textField.placeholder = placeholder
textField.autocapitalizationType = .none
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: InsetTextField, context: Context) {
uiView.text = text
uiView.textInsets = textInsets.uiInsets
uiView.isSecureTextEntry = isSecure
if context.environment.disableAutocorrection == true {
uiView.autocorrectionType = .yes
}
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: DynamicTextField
init(parent: DynamicTextField) {
self.parent = parent
super.init()
parent.textField.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged)
}
@objc func editingChanged(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
// MARK: - UITextFieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parent.onCommit?()
return true
}
}
}
Conclusion
To sum things up, SwiftUI can be used for any type of application, and I believe that it is much more efficient, readable, and fun, which is a win-win for both developers and businesses. Additionally, Apple’s effort to push SwiftUI forward indicates that it may become the main UI framework in the near future. With 5 months of intense experience, I personally don’t see any reason for developers to hold back.
Top comments (0)