Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftUI for UIKit developers

SwiftUI for UIKit developers

A candid look at fundamental changes SwiftUI brings into day-2-day work of UIKit developers. Presented at Pragma Conference 2019.

Transcript is available on my blog: https://aplus.rs/2019/swiftui-for-uikit-developers/

More Decks by Aleksandar Vacić (Radiant Tap)

Other Decks in Programming

Transcript

  1. Aleksandar Vacić @radiantav
    at Pragma Conf 2019
    SwiftUI for
    UIKit developers

    View Slide

  2. All new UI framework
    Powered by Swift 5.1
    Declarative layout
    Single point of truth

    View Slide

  3. — Team Rx, after the WWDC 2019 keynote
    (also team Redux, React etc)

    View Slide

  4. Meanwhile, at
    team UIKit : !

    View Slide

  5. !
    Principles & Patterns

    Layout & Rendering

    Navigation

    Data flow & management

    UIKit ➜ SwiftUI "
    Interactions

    View Slide

  6. • Class-inheritance based
    • Declarative and/or imperative layout
    • Obvious, reachable view hierarchy
    • MVC, Target-Action, Delegate
    UIKit principles & patterns
    UIKit

    View Slide

  7. • Lightweight, composable independent views
    • Exclusively declarative layout
    • Black box rendering
    • Reference data semantics, automatic observers
    SwiftUI principles & patterns
    SwiftUI

    View Slide

  8. Layout & Rendering

    View Slide

  9. UIKit
    UIResponder
    ⤷ UIViewController → MyVC
    ⤷ UINC & friends
    ⤷ UIView → MyView
    ⤷ UIControl → MyControl
    ⤷ UILabel
    ⤷ UITextField

    View Slide

  10. SwiftUI
    MyView: View
    Text: View
    Image: View
    TextField: View
    Button: View
    List: View
    public protocol View {
    associatedtype Body : View
    var body: Self.Body { get }
    }

    View Slide

  11. UIKit
    final class GreetingView: UIView {
    var name: String? {
    didSet { label.text = name }
    }
    private var label: UILabel!
    override init(frame: CGRect) {
    super.init(frame: frame)
    let label = UILabel(frame: frame.inset(by: layoutMargins))
    addSubview(label)
    self.label = label
    }
    required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }
    }

    View Slide

  12. SwiftUI
    struct Greeting: View {
    var name: String
    var body: some View {
    Text(name)
    }
    }

    View Slide

  13. (1) What do we lose switching from
    imperative to declarative?
    (2)When do you really need this
    ability to (re)build your subview
    hierarchy at will, at runtime?

    View Slide

  14. SwiftUI’s declarative approach
    is the correct approach.
    #

    View Slide

  15. SwiftUI
    struct Greeting: View {
    var name: String
    var body: some View {
    Text(name)
    }
    }

    View Slide

  16. UIKit
    final class GreetingView: UIView {
    var name: String? {
    didSet { label.text = name }
    }
    private var label: UILabel!
    override init(frame: CGRect) {
    super.init(frame: frame)
    let label = UILabel(frame: frame.inset(by: layoutMargins))
    addSubview(label)
    self.label = label
    }
    required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
    }
    }

    View Slide

  17. UIKit
    final class GreetingView: UIView {
    var name: String? {
    didSet { label.text = name }
    }
    @IBOutlet private var label: UILabel!
    }

    View Slide

  18. SwiftUI + Instant Previews
    are far worse tool than
    UIKit + Interface Builder
    for beginners.

    View Slide

  19. UIKit

    View Slide

  20. SwiftUI

    View Slide

  21. SwiftUI

    View Slide

  22. SwiftUI

    View Slide

  23. SwiftUI

    View Slide

  24. SwiftUI

    View Slide

  25. Instant Previews in Xcode 11 truly shine 

    once you already know SwiftUI.

    View Slide

  26. SwiftUI’s main advantage over UIKit
    is ridiculously simple
    composing of controls.
    $

    View Slide

  27. Navigation

    View Slide

  28. UIKit
    let targetVC = TargetController()
    show(targetVC, sender: self)

    View Slide

  29. SwiftUI
    let targetView = TargetView()
    NavigationLink(destination: targetView) {
    Text("tap me")
    }

    View Slide

  30. SwiftUI

    View Slide

  31. SwiftUI

    View Slide

  32. SwiftUI
    let targetView = TargetView()
    NavigationLink(destination: targetView) {
    Text("tap me")
    }

    View Slide

  33. SwiftUI
    NavigationLink(destination: TargetView()) {
    Text("tap me")
    }

    View Slide

  34. p.s. Coordinator pattern rulez!
    Navigation is equally horrible
    in both UIKit and SwiftUI.
    %

    View Slide

  35. Interactions

    View Slide

  36. SwiftUI
    Text("Tap me!")
    .onTapGesture {
    // do something
    }
    Image("some-image")
    .onLongPressGesture {
    // do something
    }

    View Slide

  37. UIKit
    UIResponder
    ⤷ UIViewController → MyVC
    ⤷ UIView → MyView

    View Slide

  38. UIKit
    UIResponder().next
    UIView().next = .superview
    UIViewController().next = .parent ?? .view.superview
    = upwards pathway through the UI hierarchy

    View Slide

  39. UIKit
    Responder chain is UIKit’s
    least used superpower.

    View Slide

  40. View Slide

  41. UIKit
    extension UIResponder {
    var fieldsController: FieldsController? {
    if let c = self as? FieldsController { return c }
    if let c = next as? FieldsController { return c }
    return next?.fieldsController
    }
    }
    github.com/radianttap/Fields

    View Slide

  42. UIKit
    extension UIResponder {
    @objc func accountPerformLogout(
    onQueue queue: OperationQueue? = .main,
    sender: Any? = nil,
    callback: @escaping (Error?) -> Void = {_ in}
    ){
    coordinatingResponder?.accountPerformLogout(
    onQueue: queue,
    sender: sender,
    callback: callback)
    }
    }
    github.com/radianttap/Coordinator

    View Slide

  43. SwiftUI
    var body: some View {
    NavigationView {
    List {
    ...
    }
    .listStyle(GroupedListStyle())
    .navigationBarTitle("Menu")
    }
    }

    View Slide

  44. SwiftUI
    https://developer.apple.com/videos/play/wwdc2019/216/?time=3160
    “And then we can use the navigationBarTitle
    modifier to produce that large beautiful title for our form.
    Now this modifier is a little bit special.
    It provides information that’s able to be interpreted by a
    NavigationView ancestor.
    …this is an example of one that flows information
    upwards using something called preferences.”
    SwiftUI Essentials

    View Slide

  45. SwiftUI
    https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

    View Slide

  46. Data Flow & Management

    View Slide

  47. View
    View
    View
    isPlaying: Bool isPlaying: Bool
    https://developer.apple.com/videos/play/wwdc2019/226
    Data Flow through SwiftUI

    View Slide

  48. View
    View
    View
    isPlaying: Bool
    SwiftUI
    &isPlaying
    @State
    @Binding

    View Slide

  49. View
    View
    View
    isPlaying: Bool isPlaying: Bool

    View Slide

  50. component
    View
    View
    View
    isPlaying: Bool isPlaying: Bool

    View Slide

  51. control component
    View
    View
    View
    isPlaying: Bool isPlaying: Bool

    View Slide

  52. control component
    View
    View
    View
    isPlaying: Bool isPlaying: Bool
    model

    View Slide

  53. controller
    control component
    View
    View
    View
    isPlaying: Bool isPlaying: Bool
    model
    UIKit
    Model View Controller

    View Slide

  54. View View View View View View
    Model Environment
    https://developer.apple.com/videos/play/wwdc2019/226
    Data Flow through SwiftUI

    View Slide

  55. SwiftUI
    final class SomeService: ObservableObject {}
    struct MainView: View {
    @EnvironmentObject var service: SomeService
    }
    class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    lazy var service = SomeService()
    func scene(_ scene: UIScene, willConnectTo…) {
    let mainView = MainView().environmentObject(service)
    }
    }
    &

    View Slide

  56. SwiftUI
    final class DataManager: ObservableObject {
    @Published private(set) var events: [Event] = []
    }
    struct EventsList: View {
    @ObservedObject var dataStore: DataManager
    private var events: [Event] { return dataStore.events }
    } '

    View Slide

  57. final class DataManager {
    private(set) var events: [Event] = [] {
    didSet {
    NotificationCenter.default.post(
    name: NSNotification.Name("DataManagerDidUpdateEventsNotification"),
    object: self
    )
    }
    }
    }
    final class EventsListController: UIViewController {
    var events: [Event] = [] {
    didSet { render() }
    }
    override func viewDidLoad() {
    super.viewDidLoad()
    render()
    setupNotificationHandlers()
    }
    }
    private extension EventsListController {
    func render() {}
    func setupNotificationHandlers() {
    NotificationCenter.default.addObserver(forName: NSNotification.Name("DataManagerDidUpdateEventsNotification"), object:
    [weak self] notification in
    guard let self = self else { return }
    guard let dataManager = notification.object as? DataManager else { return }
    self.events = dataManager.events
    }
    }
    UIKit

    View Slide

  58. SwiftUI
    final class DataManager: ObservableObject {
    @Published private(set) var events: [Event] = []
    }
    struct EventsList: View {
    @ObservedObject var dataStore: DataManager
    private var events: [Event] { return dataStore.events }
    }

    View Slide

  59. SwiftUI
    final class DataManager: ObservableObject {
    let eventsWillChange = ObservableObjectPublisher()
    private(set) var events: [Event] = [] {
    willSet {
    eventsWillChange.send()
    }
    }
    }
    struct EventsList: View {
    @ObservedObject var dataStore: DataManager
    private var events: [Event] { return dataStore.events }
    }
    $

    View Slide

  60. Ok. Where we are, then?

    View Slide

  61. SwiftUI is UI framework where you
    独declare layout but do not control the
    render part
    独declare data you need but don’t
    manage them

    View Slide

  62. When you hit roadblock in
    UIKit, you have options to
    work around somehow.
    Not so in SwiftUI, you must
    wait for next iOS update.

    View Slide

  63. Today, SwiftUI is a
    pretty solid ver 0.7

    It’s not ready.

    View Slide

  64. SwiftUI is not the framework
    you want to build your app on,
    today.

    View Slide

  65. If you start new projects in
    next 12 months,
    I recommend to use UIKit,
    even for iOS 13.

    View Slide

  66. Don’t fear you’ll be left
    behind, it’s way too early.
    No one is an expert, yet.

    View Slide

  67. With hope this was useful
    — Thank you.

    View Slide

  68. SpeakerDeck.com / radianttap
    aplus.rs
    Aleksandar Vacić, radianttap.com
    @radiantav
    slides
    dev blog
    contact
    / radianttap
    open source

    View Slide