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

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

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

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

Avatar for sakaue

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