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

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

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

sakaue

May 19, 2021
Tweet

More Decks by sakaue

Other Decks in Programming

Transcript

  1. 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
  2. 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) } }
  3. 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() } }
  4. ભҠ 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") } } }
  5. ભҠ 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) } }
  6. 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) }
  7. 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 } }
  8. 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) } } } }
  9. 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
  10. ࡞ͬͨ 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
  11. ૉ௚ʹ 
 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)) } }
  12. $PMMFDUJPO7JFXY4XJGU6* 6*$PMMFDUJPO7JFX$FMM 6*$PMMFDUJPO7JFX 4XJGU6*7JFX 4FDUJPO*%  JE4FDUJPO*%  EBUB-JTUσʔλ഑ྻ 

    EBUB*EσʔλͷҰҙੑΛද͢,FZ1BUI  DPOUFOUσʔλΛݩʹੜ੒͢Δ7JFX 4UBUF 4FDUJPO $FMM
  13. 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 }
  14. 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)) } } }