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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Mike Apurin
March 29, 2024
Programming
950
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
SwiftUIに最適のアーキテクチャ
Mike Apurin
March 29, 2024
More Decks by Mike Apurin
See All by Mike Apurin
iOSDC 2024
auramagi
3
1.5k
SwiftUIとUIKitを仲良くさせる
auramagi
3
6.2k
SwiftUI Layout
auramagi
1
880
SwiftUIでUIViewを使うときのレイアウト処理 / Layout process when using UIKit view in SwiftUI
auramagi
0
760
Other Decks in Programming
See All in Programming
【SRE NEXT 2026 Lunch Session】一人目専任SREの立ち上げを加速する ― AIと進めたオンボーディングで2分を0.04秒にした話
pkshadeck
PRO
0
2.3k
アルゴリズムは何を圧縮しているのか ─ Haskell から育った「圧縮代数」というメンタルモデル
naoya
16
3.3k
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
370
【やさしく解説 設計編 #1】「ドメイン駆動」と「実装駆動」ってなに? 〜設計の考え方を、たとえ話で学ぼう〜
panda728
PRO
1
110
act2-costs.pdf
sumedhbala
0
110
Generative UI & AI-Assistants for Your Angular Solutions
manfredsteyer
PRO
0
100
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
310
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
360
Haskell/Servantを通してWebミドルウェアを捉え直す
pizzacat83
1
480
過去最大のMCPアップデート! 2026-07-28 RC版の謎に迫る
licux
6
460
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
1.1k
SREは、MCPとSRE Agentをこう使え!
kazumax55
0
150
Featured
See All Featured
My Coaching Mixtape
mlcsv
0
170
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
Code Review Best Practice
trishagee
74
20k
From π to Pie charts
rasagy
0
230
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
650
Evolving SEO for Evolving Search Engines
ryanjones
0
240
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
56k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
290
Joys of Absence: A Defence of Solitary Play
codingconduct
1
410
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
2k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Transcript
SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ Mike Apurin
None
SwiftUIʹ࠷దͷ ΞʔΩςΫνϟͱ Կ͔ʁ
MVVM? – MV? – TCA? – MVC? – MVP? –
VIPER? –
ͦͷલʹ
ۜͷؙͳ͍
ΞʔΩςΫνϟҊ ݅ͷχʔζΛߟྀ͢ ͖
SwiftUIҎ֎ʹUIKitΘΕ͍ͯΔ͔ʁ – ΞʔΩςΫνϟ͕ղܾ͍ͯ͠Δ՝χʔζ ʹϚον͍ͯ͠Δ͔ – νʔϜϝϯόʔ͜ͷΞʔΩςΫνϟʹ׳Ε ͍ͯΔ͔ – etc –
ཧͱݱ࣮ဃ͠ ͍ͯΔ
UIKitͱSwiftUIͷޓ ੑ
UIKitͱSwiftUIͷޓ ੑ UINavigationController ͰUIHostingControllerΛ push͢ΔΞχϝʔγϣϯ͕όά Δ let vc = UIHostingController(rootView:
SwiftUIScreen()) navigationController.show(vc, sender: nil)
SwiftUIͷதͰNavigationLinkΛ౿Ήͱൃੜ ͠ͳ͍ – Ҋ݅ͷ߹্ɺUINavigationControllerΛ ͏͔͠ͳ͍ – ͨͩͦ͏͢Δͱɺ৽ΊͷNavigationStack UINavigationControllerͱޓੑ͕ͳ͍͔ Β NavigationLink(value:)
͑ͳ͍ –
ࠓߋݹ͍ NavigationLink(destination:) ͍ ͨ͘ͳ͍ͷͰࣗલͰ NavigationLink(value:) Λ NavigationStack͕ͳͯ͑͘ΔΑ͏ʹόοΫ ϙʔτ͍ͯ͠·͢ github.com/auramagi/Destinations
SwiftPMͷػೳ
None
Development AssetsΛѻ͏ػೳ͕ͳ͍ DebugϏϧυϓϨϏϡʔʹ͏ը૾ ϑΝΠϧ ReleaseϏϧυ͔Βࣗಈతʹഉআ͞ΕΔ
͍͔ͭ͘ϫʔΫΞϥϯυΛࢼ͍ͯ͠Δ Package.swiftͰΓସ͑͢Δʢඇਪʣ – XcodeͷLaunch argumentͰPackage.swiftΛΓସ͑Δ – ϏϧυϓϥάΠϯ – github.com/auramagi/ManageDevelopmentAssetsPlugin –
खಈͰϑΥϧμΛೖΕସ͑Δ – github.com/auramagi/swiftui-first-sample –
ࢲ͕ٻΊ͍ͯΔ SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ
ཁ݅ SwiftUI First – Xcode Previews͕ਏ͘ͳ͍ – ϚϧνΠϯυ + Ϛϧνεςʔτ͕Մೳ
– UIKit, App Intentsߟ͑ΒΕ͍ͯΔ – ֎෦ϥΠϒϥϦͷґଘ͕ೱ͘ͳ͍ –
SwiftUI First SwiftUIͷঢ়ଶཧ͕͑Δ – SwiftUIͷϥΠϑαΠΫϧͰΓཱͬͯ ͍Δ – SwiftUIͰѻ͍͍͢ –
Previews͕ਏ͘ͳ͍ DI͕ͪΌΜͱ͍ͯ͠Δ – UIϨΠϠʔʹॏ͍ґଘΛஔ͔ͳ͍ – ϞοΫ͍͢͠ –
ϚϧνΠϯυՄೳ ෳΠϯυͰͦΕͧΕϩάΠϯঢ়ଶ ͕ҧ͏ͷઃܭՄೳʁ –
UIKit, App Intents DIೖͷํͲ͏ͳΔʁ – ઃܭͷॊೈੑ͕ඞཁ –
֎෦ϥΠϒϥϦͷґଘ ֎෦ϥΠϒϥϦΛத৺ʹΞϓϦΛઃܭͨ͠Β͍͍͔͕ ৻ॏʹݕ౼͠·͠ΐ͏ – ։ൃࢭ·ͬͯόά͕ग़ͨΒͲ͏͢Δʁ – ണ͕͢ͱ͖ʹͲΕ͚ͩେมʁ – Apple͕৽͍͠APIΛग़ͨ͠Β࠾༻ʹ͠ͳ͍͔ʁ –
github.com/auramagi/ swiftui-first-sample
Naive MV struct ContentView: View { let api: APIClient @State
var products: [Product] = [] var body: some View { ForEach(products) { product in Button("Buy \(product.name)") { Task { await api.execute( BuyProductsRequest(product) ) } } } .task { products = await api.execute( GetProductsRequest() ) } } }
Naive MV ີ݁߹ – ϓϨϏϡʔແཧ – ϏϡʔϩδοΫ࠶༻ੑ͕͍ – ґଘೖ͕ߟ͑ΒΕ͍ͯͳ͍ –
None
None
DIೖ struct AppActions { struct Products { var buy: (Product)
async -> Void var refresh: () async -> Void } } ActionsΛΓ͚Δ –
DIೖ struct AppState { final class Products: ObservableObject { @Published
var products: [Product] = [] } } StateΛΓ͚Δ –
struct ContentView: View { @ObservedObject var state: AppState.Products let actions:
AppActions.Products var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
ΠχγϟϥΠβͰActionsͱStateೖՄೳʹͨ͠ ͕ɺதͷ࣮มΘΔͷ͕كͳͷͰຖճຖճࢦఆ͢Δ ͷ໘ EnvironmentͰґଘΛୡͤ͞Δ – State @EnvironmentObject Ͱ – Actions
@Environment Ͱ –
StateͱActionsΛଋͶͯ Dependency ͱͯ͠ఆٛ struct AppState { final class Products: ObservableObject
{ ... } var products = Products() } struct AppActions { struct Products { ... } var products: Products } extension EnvrionmentValues { var appActions: AppActions { ... } } struct AppDependency { var state: AppState var actions: AppActions }
Dependency Λอ࣋͢ΔίϯςφΛ ఆٛ protocol AppContainer { var app: AppDependency {
get set } } MockAppContainer ͱ LiveAppContainer Λ ४උ
struct ContentView: View { @EnvironmentObject var state: AppState.Products @Environment(\.appActions.products) let
actions var body: some View { ForEach(state.products) { product in Button("Buy \(product.name)") { Task { await actions.buy(product) } } } .task { await actions.refresh() } } }
ೖΛ͘͢͢͠Δ public protocol ViewInjectable { typealias Content = ViewDependencyModifier<Self>.Content associatedtype
InjectedBody: View func inject(content: Content) -> InjectedBody } public struct ViewDependencyModifier<D: ViewInjectable>: ViewModifier { let dependency: D public func body(content: Content) -> some View { dependency.inject(content: content) } } extension View { public func dependency(_ dependency: some ViewInjectable) -> some View { modifier(ViewDependencyModifier(dependency: dependency)) } }
Service Λ࣮ final class ProductsService { let api: APIClient let
state: AppState.Products func buy(product: Product) async { await api.execute( BuyProductsRequest(product) ) } func refresh() async { products = await api.execute( GetProductsRequest() ) } }
LiveAppContainer Ͱଓ final class LiveAppContainer: AppContainer { struct Configuration {
... } var app: AppDependency private let api: APIClient private let productsService: ProductsService init(configuration: Configuration) { self.app = .init() self.api = ... self.productsService = .init( api: api, state: app.state.products ) app.actions.products.buy = productsService.buy(product:) app.actions.products.refresh = productsService.refresh } }
Previews #Preview { ContentView() .mockContainer(.app) } #Preview { WithMockContainer(.app) {
container in MainFlow(container: container) } } ( .mockContainer ͱ WithMockContainer ͷׂ࣮Ѫ)
ίϯςφFlowͰ͢ struct MainFlow<Container: AppContainer>: View { let container: Container var
body: some View { ContentView() .dependency(container) } }
View – ͨͩͷUI෦ – Screen – ը໘શମΛදࣔ͢ΔUI෦ – Screen ಉ࢜Λ࡞͠ͳ͍
– Flow – DependencyContainer Λอ࣋ͯ͠ೖ͢Δ – Screen Λ࡞͢Δ – ભҠ –
AppͰίϯςφΛੜ @main struct SampleApp: App { @State var container =
LiveAppContainer( configuration: ... ) var body: some Scene { WindowGroup { MainFlow(container: container) .dependency(container) } } }
None
͝ਗ਼ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠