Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
SwiftUIを新規プロダクトで用いた話
Search
sakaue
May 19, 2021
Programming
560
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SwiftUIを新規プロダクトで用いた話
sakaue
May 19, 2021
More Decks by sakaue
See All by sakaue
先端事例「Amazon Chime SDK」を用いた1on1ビデオ通話の構築
tosaka07
0
1.7k
UICollectionView 基礎編
tosaka07
0
140
Other Decks in Programming
See All in Programming
プラグインで拡張される Context をtype-safe にする難しさと設計判断
kazupon
2
590
Signal Forms: Beyond the Basics @ngBaguette 2026 in Paris
manfredsteyer
PRO
0
230
AIとASP.NET Coreで雑Webアプリを作った話
mayuki
0
320
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
2.8k
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
450
権限チェックの一貫性を型で守る TypeScript による多層防御
mnch
4
1.1k
tsserverとは何だったのか、これからどうなるのか
nowaki28
1
450
oxlintはeslint/typescript-eslintを置き換えられるのか
shomafujita
2
320
Java × distroless で 軽量なコンテナイメージを / Java on Distroless
contour_gara
0
500
Modding RubyKaigi for Myself
yui_knk
0
890
OSもどきOS
arkw
0
450
密結合なバックエンドから TypeScript のコードを生成する
kemuridama
1
740
Featured
See All Featured
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
71
40k
GitHub's CSS Performance
jonrohan
1033
470k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
440
Context Engineering - Making Every Token Count
addyosmani
9
940
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
Deep Space Network (abreviated)
tonyrice
0
160
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.2k
A Tale of Four Properties
chriscoyier
163
24k
The World Runs on Bad Software
bkeepers
PRO
72
12k
The Invisible Side of Design
smashingmag
302
52k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
The Spectacular Lies of Maps
axbom
PRO
1
790
Transcript
4XJGU6*Λ ৽نϓϩμΫτͰ༻͍ͨ $"TXJGUʙ͍·ߟ͑Δ࣍ੈͷઃܭʙ
ࣗݾհͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦ ·ͱΊ
࣍
ࣗݾհͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦ ·ͱΊ
࣍
J04%FWFMPQFS w גࣜձࣾ$". w ಈը৴ΞϓϦ w ήολʔζ൧ాͷ͍ w 'FOTJ w
ౡݝग़ ࡔ্ᠳޛ4BLBVF4IPHP UPTBLB@UFDI UPTBLB
None
None
ֹ݄՝ۚ 'FOTJɺ ͋ΒΏΔදݱ׆ಈΛ͢ΔํΛ શྗͰԠԉ͢ΔαʔϏεͰ͢ɻ ΦϑΟγϟϧαΠτ ݶఆίϛϡχςΟ άοζൢച POτʔΫ
FUD
͜Μͳײ͡ͷ ΞϓϦͰ͢
ࣗݾհͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦ ·ͱΊ
࣍
4XJGU6*ͷྑ͞ w એݴత w ࠶ར༻Λҙࣝͨ͠࡞Γ w 1SFWJFX w "VUP-BZPVU4UPSZCPBSE9JC͔Βͷղ์ w
ίʔυϨϏϡʔ͍͢͠
4XJGU6*Λ࠾༻ͨ͠ܦҢ w 8FCϢʔβʔJ04Ҏ্͕΄ͱΜͲͩͬͨ w 4XJGU6*͕ൃද͞ΕͯҰ͘Β͍ܦ͍ͬͯͯɺ ίϛϡχςΟͷݟ͋Δఔஷ·͍ͬͯͨ w ৽͍ٕ͠ज़ʹ৮ΕΔػձΛ࡞Γ͔ͨͬͨ w
"VUP-BZPVU͔Βͷղ์
։ൃώετϦʔ ։ൃελʔτ ΞϓϦϦϦʔε POϦϦʔε ݱࡏ ։ൃਓ
ਓ ਓ ਓ 4XJGU6*
ࣗݾհͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦ ·ͱΊ
࣍
Ͳ͏࡞͍͔ͬͯͬͨ ·ͣ J04ͷ4XJGU6*ͱدΓఴ͏🤝
دΓఴͬͨ݁Ռ👻 w 4XJGU6*ʹରԠͯ͠ͳ͍෦͕ଟ͍ w J04Ͱ͢Βόʔδϣϯ͝ͱʹڍಈ͕ҧ͏ w ͱ͜ΖͲ͜ΖόάͬΆ͍ڍಈ
دΓఴͬͨ݁Ռ👻 6*,JU🤝 4XJGU6*🥲
دΓఴͬͨ݁Ռ👻 6*,JU🤝 4XJGU6*🥲 6*,JUΛجຊͱ͠ɺ4XJGU6*Λ෦తʹͬͨ΄͏͕ ϦεΫ͕͍ɻ
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
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) } }
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() } }
ભҠ 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") } } }
ભҠ 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) } }
ڞଘͷϝϦοτ w ͍͔Α͏ʹͭ͘ΕΔ w τϥϯδγϣϯΞχϝʔγϣϯ w ϧʔςΟϯά w ճస੍ޚ w
Ұը໘͝ͱʹཧͰ͖Δ
ΞʔΩςΫνϟ
ΞʔΩςΫνϟ w ϓϩμΫτνʔϜʹ߹ΘͤΔ w 6OJEJSFDUJPOBM w 4PVSDFPGUSVUI w 'FOTJͰ$PNCJOFʹஔ͖ ͑ͨ3FBDUPS,JUΛࣗ࡞
IUUQTEFWFMPQFSBQQMFDPNEPDVNFOUBUJPOTXJGUVJTUBUFBOEEBUB fl PX
3FBDUPS,JU w νʔϜͰͷܦݧ͕͋ͬͨ w 6OJEJSFDUJPOBM͕4XJGU6* ʹ߹ͬͯͨ w طଘͷઃܭ͔Β4XJGU6*ʹ ߹ΘͤΔͷଟগͷख͕ؒ͋ Δ
IUUQTHJUIVCDPN3FBDUPS,JU3FBDUPS,JU
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) }
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 } }
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) } } } }
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
෦
࡞ͬͨ 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
ૉʹ 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)) } }
$PMMFDUJPO7JFXʹٻΊΔͷ w 6*$PMMFDUJPO7JFXY4XJGU6*🤔 w 6*$PMMFDUJPO7JFX$PNQPTJUJPOBM-BZPVU͍͍ͨ w 6*$PMMFDUJPO7JFX%J ff BCMF%BUB4PVSDF͍͍ͨ
$PMMFDUJPO7JFXY4XJGU6* 6*$PMMFDUJPO7JFX$FMM 6*$PMMFDUJPO7JFX 4XJGU6*7JFX 4FDUJPO*% JE4FDUJPO*% EBUB-JTUσʔλྻ
EBUB*EσʔλͷҰҙੑΛද͢,FZ1BUI DPOUFOUσʔλΛݩʹੜ͢Δ7JFX 4UBUF 4FDUJPO $FMM
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 }
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)) } } }
ࣗݾհͱϓϩμΫτ 4XJGU6*ͷྑ͞ͱ࠾༻ܦҢ Ͳ͏࡞͍͔ͬͯͬͨ w ڞଘΞʔΩςΫνϟ෦ ·ͱΊ
࣍
·ͱΊ w J04αϙʔτͭΒ͍ ˠJ04ʹͰ͖ΔͳΒ͠·͠ΐ͏ w શʹ4XJGU6*ʹدͤΔ͜ͱϦεΫ͕Ͱ͔͍ ˠ6*,JUͷಀ͛ΒΕΔઃܭΛ͠·͠ΐ͏ w
88%$ʹظʜʜ🙏
͋Γ͕ͱ͏͍͟͝·ͨ͠🙇