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

SwiftUIを新規プロダクトで用いた話

 SwiftUIを新規プロダクトで用いた話

F8fc4ec35531ef9a285c1a84591cc16c?s=128

sakaue

May 19, 2021
Tweet

Transcript

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

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

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

    ໨࣍
  4. J04%FWFMPQFS w גࣜձࣾ$". w ಈը഑৴ΞϓϦ w ήολʔζ൧ాͷ઎͍ w 'FOTJ w

    ޿ౡݝग़਎ ࡔ্ᠳޛ4BLBVF4IPHP UPTBLB@UFDI UPTBLB
  5. None
  6. None
  7. ֹ݄՝ۚ 'FOTJ͸ɺ 
 ͋ΒΏΔදݱ׆ಈΛ͢ΔํΛ 
 શྗͰԠԉ͢ΔαʔϏεͰ͢ɻ ΦϑΟγϟϧαΠτ ݶఆίϛϡχςΟ άοζൢച POτʔΫ

    FUD
  8. ͜Μͳײ͡ͷ ΞϓϦͰ͢

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

    ໨࣍
  10. 4XJGU6*ͷྑ͞ w એݴత w ࠶ར༻Λҙࣝͨ͠࡞Γ w 1SFWJFX w "VUP-BZPVU4UPSZCPBSE9JC͔Βͷղ์ w

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

    "VUP-BZPVU͔Βͷղ์
  12. ։ൃώετϦʔ  ։ൃελʔτ  ΞϓϦϦϦʔε  POϦϦʔε  ݱࡏ ։ൃਓ਺

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

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

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

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

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

  18. 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
  19. 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) } }
  20. 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() } }
  21. ભҠ 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") } } }
  22. ભҠ class SampleViewController: UIViewController { var cancellables = Set<AnyCancellable>() 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) } }
  23. ڞଘͷϝϦοτ w ͍͔Α͏ʹ΋ͭ͘ΕΔ w τϥϯδγϣϯΞχϝʔγϣϯ w ϧʔςΟϯά w ճస੍ޚ w

    Ұը໘͝ͱʹ؅ཧͰ͖Δ
  24. ΞʔΩςΫνϟ

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

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

    IUUQTHJUIVCDPN3FBDUPS,JU3FBDUPS,JU
  27. 3FBDUPS,JU public protocol ReactorCore: class { associatedtype Action associatedtype Mutation

    = Action associatedtype State func transform(action: AnyPublisher<Action, Never>) -> AnyPublisher<Action, Never> func mutate(state: State, action: Action) -> AnyPublisher<Mutation, Never> func transform(mutation: AnyPublisher<Mutation, Never>) -> AnyPublisher<Mutation, Never> func reduce(state: State, mutation: Mutation) -> State func transform(state: AnyPublisher<State, Never>) -> AnyPublisher<State, Never> } 
 class Reactor<Core: ReactorCore> { var state: CurrentValueSubject<Core.State, Never> 
 init(initialState: Core.State, core: Core) func send(_ action: Core.Action) }
  28. 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<Mutation, Never> { 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 } }
  29. 3FBDUPS,JU class SampleViewController: UIViewController { var cancellables = Set<AnyCancellable>() let

    reactor: Reactor<SampleCore> override func viewDidLoad() { super.viewDidLoad() bind(reactor: reactor) } func bind(reactor: Reactor<SampleCore>) { 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) } } } }
  30. 7JFX4UPSF @dynamicMemberLookup public class ViewStore<State, Action>: ObservableObject { public let

    objectWillChange = ObservableObjectPublisher() public private(set) var state: State { willSet { objectWillChange.send() } } public let publisher: StatePublisher<State> private var viewCancellable: AnyCancellable? private let _send: (Action) -> Void init<Core: ReactorCore>( _ reactor: Reactor<Core> ) 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<LocalState>(dynamicMember keyPath: KeyPath<State, LocalState>) -> 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
  31. ෦඼

  32. ࡞ͬͨ 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
  33. ૉ௚ʹ 
 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)) } }
  34. $PMMFDUJPO7JFXʹٻΊΔ΋ͷ w 6*$PMMFDUJPO7JFXY4XJGU6*🤔 w 6*$PMMFDUJPO7JFX$PNQPTJUJPOBM-BZPVU࢖͍͍ͨ w 6*$PMMFDUJPO7JFX%J ff BCMF%BUB4PVSDF࢖͍͍ͨ

  35. $PMMFDUJPO7JFXY4XJGU6* 6*$PMMFDUJPO7JFX$FMM 6*$PMMFDUJPO7JFX 4XJGU6*7JFX 4FDUJPO*%  JE4FDUJPO*%  EBUB-JTUσʔλ഑ྻ 

    EBUB*EσʔλͷҰҙੑΛද͢,FZ1BUI  DPOUFOUσʔλΛݩʹੜ੒͢Δ7JFX 4UBUF 4FDUJPO $FMM
  36. 3FOEFSFS public protocol CollectionRenderer { associatedtype ID: Hashable associatedtype Action

    associatedtype State func render(state: State, store: ViewStore<State, Action>) func createSections(state: State, store: ViewStore<State, Action>) -> [Section<ID>] func createLayoutSection( id: ID, environment: NSCollectionLayoutEnvironment ) -> NSCollectionLayoutSection }
  37. 3FOEFSFS class TodoListCollectionRenderer: CollectionRenderer { typealias State = ToDoListReactor.State typealias

    Store = ViewStore<State, ToDoListReactor.Action> enum ID: Int { case todo case empty } @SectionBuilder func createSections(state: State, store: Store) -> [Section<ID>] { 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<ID> { Section( id: ID.todo, data: state.todoList, dataID: \.self ) { todo in TodoItemView( Title: todo.content, done: todo.done ) .onTapGesture { store.send(.showDetail(todo)) } } }
  38.  ࣗݾ঺հͱϓϩμΫτ  4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ  Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦඼  ·ͱΊ

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

    88%$ʹظ଴ʜʜ🙏
  40. ͋Γ͕ͱ͏͍͟͝·ͨ͠🙇