Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Meanwhile, at team UIKit : !

Slide 5

Slide 5 text

! Principles & Patterns ① Layout & Rendering ② Navigation ③ Data flow & management ⑤ UIKit ➜ SwiftUI " Interactions ④

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Layout & Rendering

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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") } }

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

(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?

Slide 14

Slide 14 text

SwiftUI’s declarative approach is the correct approach. #

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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") } }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

UIKit

Slide 20

Slide 20 text

SwiftUI

Slide 21

Slide 21 text

SwiftUI

Slide 22

Slide 22 text

SwiftUI

Slide 23

Slide 23 text

SwiftUI

Slide 24

Slide 24 text

SwiftUI

Slide 25

Slide 25 text

Instant Previews in Xcode 11 truly shine 
 once you already know SwiftUI.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Navigation

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

SwiftUI

Slide 31

Slide 31 text

SwiftUI

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Interactions

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

UIKit UIResponder ⤷ UIViewController → MyVC ⤷ UIView → MyView

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

UIKit Responder chain is UIKit’s least used superpower.

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Data Flow & Management

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

View View View isPlaying: Bool isPlaying: Bool

Slide 50

Slide 50 text

component View View View isPlaying: Bool isPlaying: Bool

Slide 51

Slide 51 text

control component View View View isPlaying: Bool isPlaying: Bool

Slide 52

Slide 52 text

control component View View View isPlaying: Bool isPlaying: Bool model

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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) } } &

Slide 56

Slide 56 text

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 } } '

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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 } }

Slide 59

Slide 59 text

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 } } $

Slide 60

Slide 60 text

Ok. Where we are, then?

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

Today, SwiftUI is a pretty solid ver 0.7
 It’s not ready.

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

With hope this was useful — Thank you.

Slide 68

Slide 68 text

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