Slide 1

Slide 1 text

4XJGU6*Λ ৽نϓϩμΫτͰ༻͍ͨ࿩ $"TXJGUʙ͍·ߟ͑Δ࣍ੈ୅ͷઃܭʙ

Slide 2

Slide 2 text

ࣗݾ঺հͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼ ·ͱΊ ໨࣍

Slide 3

Slide 3 text

ࣗݾ঺հͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼ ·ͱΊ ໨࣍

Slide 4

Slide 4 text

J04%FWFMPQFS w גࣜձࣾ$". w ಈը഑৴ΞϓϦ w ήολʔζ൧ాͷ઎͍ w 'FOTJ w ޿ౡݝग़਎ ࡔ্ᠳޛ4BLBVF4IPHP UPTBLB@UFDI UPTBLB

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

ֹ݄՝ۚ 'FOTJ͸ɺ 
 ͋ΒΏΔදݱ׆ಈΛ͢ΔํΛ 
 શྗͰԠԉ͢ΔαʔϏεͰ͢ɻ ΦϑΟγϟϧαΠτ ݶఆίϛϡχςΟ άοζൢച POτʔΫ FUD

Slide 8

Slide 8 text

͜Μͳײ͡ͷ ΞϓϦͰ͢

Slide 9

Slide 9 text

ࣗݾ঺հͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼ ·ͱΊ ໨࣍

Slide 10

Slide 10 text

4XJGU6*ͷྑ͞ w એݴత w ࠶ར༻Λҙࣝͨ͠࡞Γ w 1SFWJFX w "VUP-BZPVU4UPSZCPBSE9JC͔Βͷղ์ w ίʔυϨϏϡʔ͠΍͍͢

Slide 11

Slide 11 text

4XJGU6*Λ࠾༻ͨ͠ܦҢ w 8FCϢʔβʔ͸J04Ҏ্͕΄ͱΜͲͩͬͨ w 4XJGU6*͕ൃද͞ΕͯҰ೥͘Β͍ܦ͍ͬͯͯɺ 
 ίϛϡχςΟͷ஌ݟ΋͋Δఔ౓ஷ·͍ͬͯͨ w ৽͍ٕ͠ज़ʹ৮ΕΔػձΛ࡞Γ͔ͨͬͨ w "VUP-BZPVU͔Βͷղ์

Slide 12

Slide 12 text

։ൃώετϦʔ ։ൃελʔτ ΞϓϦϦϦʔε POϦϦʔε ݱࡏ ։ൃਓ਺ ਓ ਓ ਓ 4XJGU6*཰

Slide 13

Slide 13 text

ࣗݾ঺հͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼ ·ͱΊ ໨࣍

Slide 14

Slide 14 text

Ͳ͏࡞͍͔ͬͯͬͨ ·ͣ͸ J04ͷ4XJGU6*ͱدΓఴ͏🤝

Slide 15

Slide 15 text

دΓఴͬͨ݁Ռ👻 w 4XJGU6*ʹରԠͯ͠ͳ͍෦඼͕ଟ͍ w J04಺Ͱ͢Βόʔδϣϯ͝ͱʹڍಈ͕ҧ͏ w ͱ͜ΖͲ͜ΖόάͬΆ͍ڍಈ

Slide 16

Slide 16 text

دΓఴͬͨ݁Ռ👻 6*,JU🤝 4XJGU6*🥲

Slide 17

Slide 17 text

دΓఴͬͨ݁Ռ👻 6*,JU🤝 4XJGU6*🥲 6*,JUΛجຊͱ͠ɺ4XJGU6*Λ෦෼తʹ࢖ͬͨ΄͏͕ ϦεΫ͕௿͍ɻ

Slide 18

Slide 18 text

6*,JUͱ4XJGU6*ͷڞଘ w 6*7JFX$POUSPMMFSʹ 
 4XJGU6*7JFXΛͷ͚ͬΔ w ભҠ͸6*,JUΛ࢖͏ w 7JFX.PEFM͔Β 
 ભҠΠϕϯτΛड͚औΔͷ͸ 6*7JFX$POUSPMMFS 6*7JFX$POUSPMMFS 4XJGU6* 7JFX 7JFX.PEFM

Slide 19

Slide 19 text

4XJGU6*7JFXΛ7$ʹͷ͚ͬΔ class SampleViewModel: ObservableObject { @Published var title: String = "" func fetch() { self.title = "Hello, World!" } } struct SampleView: View { @ObservedObject var viewModel: SampleViewModel var body: some View { Text(viewModel.title) } }

Slide 20

Slide 20 text

4XJGU6*7JFXΛ7$ʹͷ͚ͬΔ class SampleViewController: UIViewController { let viewModel = SampleViewModel() override func viewDidLoad() { super.viewDidLoad() let vc = UIHostingController( rootView: SampleView(viewModel: viewModel) ) addChild(vc) view.addSubview(vc.view) NSLayoutConstraint.activate([/* ... */]) vc.didMove(toParent: self) viewModel.fetch() } }

Slide 21

Slide 21 text

ભҠ class SampleViewModel: ObservableObject { @Published var presentDetail: String? func presentDetail(id: String) { presentDetail = id } } struct SampleView: View { @ObservedObject var viewModel: SampleViewModel var body: some View { Button("present to Detail") { viewModel.presentDetail(id: "1") } } }

Slide 22

Slide 22 text

ભҠ class SampleViewController: UIViewController { var cancellables = Set() let viewModel = SampleViewModel() override func viewDidLoad() { super.viewDidLoad() viewModel.$presentDetail .compactMap { $0 } .sink { [weak self] id in self?.present( DetailViewController(id: id), animated: true, completion: nil ) } .store(in: &cancellables) } }

Slide 23

Slide 23 text

ڞଘͷϝϦοτ w ͍͔Α͏ʹ΋ͭ͘ΕΔ w τϥϯδγϣϯΞχϝʔγϣϯ w ϧʔςΟϯά w ճస੍ޚ w Ұը໘͝ͱʹ؅ཧͰ͖Δ

Slide 24

Slide 24 text

ΞʔΩςΫνϟ

Slide 25

Slide 25 text

ΞʔΩςΫνϟ w ϓϩμΫτ΍νʔϜʹ߹ΘͤΔ w 6OJEJSFDUJPOBM w 4PVSDFPGUSVUI w 'FOTJͰ͸$PNCJOFʹஔ͖׵ ͑ͨ3FBDUPS,JUΛࣗ࡞ IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTXJGUVJTUBUFBOEEBUB fl PX

Slide 26

Slide 26 text

3FBDUPS,JU w νʔϜͰͷܦݧ͕͋ͬͨ w 6OJEJSFDUJPOBM͕4XJGU6* ʹ߹ͬͯͨ w طଘͷઃܭ͔Β4XJGU6*ʹ ߹ΘͤΔͷ͸ଟগͷख͕ؒ͋ Δ IUUQTHJUIVCDPN3FBDUPS,JU3FBDUPS,JU

Slide 27

Slide 27 text

3FBDUPS,JU public protocol ReactorCore: class { associatedtype Action associatedtype Mutation = Action associatedtype State func transform(action: AnyPublisher) -> AnyPublisher func mutate(state: State, action: Action) -> AnyPublisher func transform(mutation: AnyPublisher) -> AnyPublisher func reduce(state: State, mutation: Mutation) -> State func transform(state: AnyPublisher) -> AnyPublisher } 
 class Reactor { var state: CurrentValueSubject 
 init(initialState: Core.State, core: Core) func send(_ action: Core.Action) }

Slide 28

Slide 28 text

3FBDUPS,JU class SampleCore: ReactorCore { enum Action { case increment, decrement } enum Mutation { case setNumber(Int) } struct State { var number: Int } func mutate(state: State, action: Action) -> AnyPublisher { switch action { case .increment: return Just(Mutation.setNumber(state.number + 1)).eraseToAnyPublisher() case .decrement: return Just(Mutation.setNumber(state.number - 1)).eraseToAnyPublisher() } } func reduce(state: State, mutation: Mutation) -> State { var state = state switch mutation { case .setNumber(let number): state.number = number } return state } }

Slide 29

Slide 29 text

3FBDUPS,JU class SampleViewController: UIViewController { var cancellables = Set() let reactor: Reactor override func viewDidLoad() { super.viewDidLoad() bind(reactor: reactor) } func bind(reactor: Reactor) { let store = ViewStore(reactor) store.publisher.number .sink { (number) in // UIKit ʹόΠϯυ } .store(in: &cancellables) let view = UIHostingController( rootView: SampleView(viewStore: store) ) // addSubView... } } struct SampleView: View { @ObservedObject var viewStore: ViewStore< SampleCore.State, SampleCore.Action > var body: some View { HStack { Button("-") { viewStore.send(.decrement) } Text("\(viewStore.number)") Button("-") { viewStore.send(.increment) } } } }

Slide 30

Slide 30 text

7JFX4UPSF @dynamicMemberLookup public class ViewStore: ObservableObject { public let objectWillChange = ObservableObjectPublisher() public private(set) var state: State { willSet { objectWillChange.send() } } public let publisher: StatePublisher private var viewCancellable: AnyCancellable? private let _send: (Action) -> Void init( _ reactor: Reactor ) where Core.State == State, Core.Action == Action { publisher = StatePublisher(reactor.$state) state = reactor.state _send = { [weak reactor] in reactor?.send($0) } viewCancellable = publisher .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] in self?.state = $0 }) } public subscript(dynamicMember keyPath: KeyPath) -> LocalState { state[keyPath: keyPath] } public func send(_ action: Action) { DispatchQueue.main.async { [weak self] in self?._send(action) } } w 5$"Ͱ࢖ΘΕ͍ͯΔ w EZOBNJD.FNCFS-PPLVQ w ॏෳͷ࡟আ w #JOEJOH IUUQTHJUIVCDPNQPJOUGSFFDPTXJGUDPNQPTBCMFBSDIJUFDUVSF

Slide 31

Slide 31 text

෦඼

Slide 32

Slide 32 text

࡞ͬͨ w 5FYU w #VUUPO w 5FYU'JFME w *NBHF w ͱ͔ˠҎ֎ͷ෦඼ 4XJGU6*Ͱ࡞ͬͨ෦඼ ࡞Βͳ͔ͬͨ w 5BC7JFX w 8FC7JFX w 5FYU7JFX w $PMMFDUJPO7JFX w $VTUPN7JFXT w 6*,JU-JCSBSZ -PUUJF

Slide 33

Slide 33 text

ૉ௚ʹ 
 6*7JFX3FQSFTFOUBCMF 6*7JFX$POUSPMMFS3FQSFTFOUBCMF Λ࢖͍·͠ΐ͏ ඇରԠͰ΋4XJGU6*ʹ૊ΈࠐΈ͍ͨ struct WebView: UIViewRepresentable { var url: URL func makeUIView(context: Context) -> WKWebView { return WKWebView(frame: .zero) } func updateUIView(_ uiView: WKWebView, context: Context) { uiView.load(URLRequest(url: url)) } }

Slide 34

Slide 34 text

$PMMFDUJPO7JFXʹٻΊΔ΋ͷ w 6*$PMMFDUJPO7JFXY4XJGU6*🤔 w 6*$PMMFDUJPO7JFX$PNQPTJUJPOBM-BZPVU࢖͍͍ͨ w 6*$PMMFDUJPO7JFX%J ff BCMF%BUB4PVSDF࢖͍͍ͨ

Slide 35

Slide 35 text

$PMMFDUJPO7JFXY4XJGU6* 6*$PMMFDUJPO7JFX$FMM 6*$PMMFDUJPO7JFX 4XJGU6*7JFX 4FDUJPO*% JE4FDUJPO*% EBUB-JTUσʔλ഑ྻ EBUB*EσʔλͷҰҙੑΛද͢,FZ1BUI DPOUFOUσʔλΛݩʹੜ੒͢Δ7JFX 4UBUF 4FDUJPO $FMM

Slide 36

Slide 36 text

3FOEFSFS public protocol CollectionRenderer { associatedtype ID: Hashable associatedtype Action associatedtype State func render(state: State, store: ViewStore) func createSections(state: State, store: ViewStore) -> [Section] func createLayoutSection( id: ID, environment: NSCollectionLayoutEnvironment ) -> NSCollectionLayoutSection }

Slide 37

Slide 37 text

3FOEFSFS class TodoListCollectionRenderer: CollectionRenderer { typealias State = ToDoListReactor.State typealias Store = ViewStore enum ID: Int { case todo case empty } @SectionBuilder func createSections(state: State, store: Store) -> [Section] { if state.todoList.isEmpty { empty() } else { todo(state: state, store: store) } } func createLayoutSection(id: ID, env: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { switch id { case .list: return .list(itemWidth: .fractionalWidth(1.0), itemHeight: .estimated(.s222)) case .empty: return .list(itemWidth: .fractionalWidth(1.0), itemHeight: .fractionalHeight(1.0)) } } } func todo(state: State, store: Store) -> Section { Section( id: ID.todo, data: state.todoList, dataID: \.self ) { todo in TodoItemView( Title: todo.content, done: todo.done ) .onTapGesture { store.send(.showDetail(todo)) } } }

Slide 38

Slide 38 text

ࣗݾ঺հͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼ ·ͱΊ ໨࣍

Slide 39

Slide 39 text

·ͱΊ w J04αϙʔτ͸ͭΒ͍ 
 ˠJ04ʹͰ͖ΔͳΒ͠·͠ΐ͏ w ׬શʹ4XJGU6*ʹدͤΔ͜ͱ͸ϦεΫ͕Ͱ͔͍ 
 ˠ6*,JU΁ͷಀ͛ΒΕΔઃܭΛ͠·͠ΐ͏ w 88%$ʹظ଴ʜʜ🙏

Slide 40

Slide 40 text

͋Γ͕ͱ͏͍͟͝·ͨ͠🙇