From UIKit to SwiftUI

fiser33

Jakub Fišer

Posted on December 11, 2021

From UIKit to SwiftUI

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

    …
}
Enter fullscreen mode Exit fullscreen mode

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Custom shape for progress bar

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)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

References

  1. SwiftUI Views - Quick Start [Big Mountain Studio]
  2. All SwiftUI property wrappers explained and compared [Hacking with swift]
  3. Demystify SwiftUI [Apple]
  4. SwiftUI in production [PSPDFKit]
💖 💪 🙅 🚩
fiser33
Jakub Fišer

Posted on December 11, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related