Slide 1

Slide 1 text

MVVM with Combine @akifumi

Slide 2

Slide 2 text

About me ● Name ○ Akifumi Fukaya ● Company ○ Merpay, Inc. (2018/06 ~) ● Role ○ Software Engineer (iOS) ● Account ○ Twitter: @akifumifukaya ○ Facebook: Akifumi Fukaya ○ Github: akifumi 2

Slide 3

Slide 3 text

What is Combine? https://developer.apple.com/documentation/combine 3

Slide 4

Slide 4 text

Combine Features ● Generic ● Type safe ● Composition first ● Request driven https://developer.apple.com/videos/play/wwdc2019/722/ 4

Slide 5

Slide 5 text

Key Concepts ● Publishers ● Subscribers ● Operators https://developer.apple.com/videos/play/wwdc2019/722/ 5

Slide 6

Slide 6 text

Publishers ● Declares that a type can transmit a sequence of values over time. ● Defines how values and errors are produced ● Value type -> `Struct` ● Allows registration of a `Subscriber` https://developer.apple.com/videos/play/wwdc2019/722/ 6

Slide 7

Slide 7 text

Subscribers ● A protocol that declares a type that can receive input from publisher. ● Receives values and a completion ● Reference type -> `Class` https://developer.apple.com/videos/play/wwdc2019/722/ 7

Slide 8

Slide 8 text

Operator ● Adopts `Publisher` ● Describes a behavior for changing values ● Subscribes to a `Publisher` (“upstream”) ● Sends result to a `Subscriber` (“downstream”) ● Value type -> `Struct` https://developer.apple.com/videos/play/wwdc2019/722/ 8

Slide 9

Slide 9 text

How to actualize MVVM with Combine https://github.com/akifumi/mvvm-with-combine-in-swiftui/blob/master/CombineSample/ContentView.swift 9

Slide 10

Slide 10 text

What are requirements? ● Input text into TextField ● Validate input text ● Separate view and model as ViewModel ● Bind view and ViewModel ● Content of TextField is two way binding 10

Slide 11

Slide 11 text

Add TextField to ContentView struct ContentView : View { @State private var username: String = "" var body: some View { VStack { TextField($username, placeholder: Text("Placeholder"), onEditingChanged: { (changed) in print("onEditingChanged: \(changed)") }, onCommit: { print("onCommit") }) } .padding(.horizontal) } } 11

Slide 12

Slide 12 text

Create ViewModel final class ContentViewModel : BindableObject { var didChange = PassthroughSubject() var username: String = "" { didSet { didChange.send(()) } } } 12

Slide 13

Slide 13 text

Create publisher to validate username final class ContentViewModel : BindableObject { ︙ private let usernameSubject = PassthroughSubject() private var validatedUsername: AnyPublisher { return usernameSubject .debounce(for: 0.5, scheduler: RunLoop.main) // Currently doesn't work .removeDuplicates() .flatMap { (username) -> AnyPublisher in Publishers.Future { (promise) in // FIXME: API request if 1...10 ~= username.count { promise(.success(username)) } else { promise(.success(nil)) } } .eraseToAnyPublisher() } .eraseToAnyPublisher() } } 13

Slide 14

Slide 14 text

Send value when username is changed final class ContentViewModel : BindableObject { var didChange = PassthroughSubject() var username: String = "" { didSet { guard oldValue != username else { return } usernameSubject.send(username) didChange.send(()) } } ︙ } 14

Slide 15

Slide 15 text

Create ContentViewModel.StatusText final class ContentViewModel : BindableObject { ︙ struct StatusText { let content: String let color: Color } var status: StatusText = StatusText(content: "NG", color: .red) { didSet { didChange.send(()) } } } 15

Slide 16

Slide 16 text

Create onApprear() interface to ViewModel final class ContentViewModel : BindableObject { ︙ lazy var onAppear: () -> Void = { [weak self] in _ = self?.validatedUsername .sink(receiveValue: { (value) in if let value = value { self?.username = value } else { print("validatedUsername.receiveValue: Invalid username") } }) // Update StatusText _ = self?.validatedUsername .map { (value) -> StatusText in (value != nil) ? StatusText(content: "OK", color: .green) : StatusText(content: "NG", color: .red) } .sink(receiveValue: { [weak self] (value) in self?.status = value }) } } 16

Slide 17

Slide 17 text

Bind ContentViewModel to ContentView struct ContentView : View { @ObjectBinding var viewModel: ContentViewModel var body: some View { VStack { HStack { Text($viewModel.status.value.content) .color($viewModel.status.value.color) Spacer() } TextField($viewModel.username, placeholder: Text("Placeholder"), onEditingChanged: { (changed) in print("onEditingChanged: \(changed)") }, onCommit: { print("onCommit") }) } .padding(.horizontal) .onAppear(perform: viewModel.onAppear) } } 17

Slide 18

Slide 18 text

Demo 18

Slide 19

Slide 19 text

Wrap up ● Introduction of Combine ● Created sample codes using SwiftUI, Combine & MVVM as one of the architecture ideas ● Currently, some features does not work ○ NotificationCenter ○ Schedule ○ @Published ○ Not implemented some features will be released in next version ● Let’s discuss about best practice of SwiftUI architecture ● Samples ○ SwiftUI: https://github.com/akifumi/mvvm-with-combine-in-swiftui ○ UIViewController: https://github.com/akifumi/mvvm-with-combine-in-uiviewcontroller 19

Slide 20

Slide 20 text

Related sessions ● Introducing Combine ○ https://developer.apple.com/videos/play/wwdc2019/722/ ● Combine in Practice ○ https://developer.apple.com/videos/play/wwdc2019/721/ ● Data Flow Through SwiftUI ○ https://developer.apple.com/videos/play/wwdc2019/226/ 20

Slide 21

Slide 21 text

Thank you for your attention.