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
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.1k
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
脅威をエンジニアリングの糧にして――現場編 / Turning Threats into Engineering Fuel — Field Edition
nrslib
0
280
Strategic Design in the Frontend: Moduliths & Micro Frontends @DDDEurope
manfredsteyer
PRO
0
100
Composerを使ったサプライチェーン攻撃の様子を眺めてみる #phpstudy
o0h
PRO
2
250
Signal Forms: Details & Live Coding @enterJS 2026 in Mannheim
manfredsteyer
PRO
0
140
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
6.7k
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
560
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
700
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
190
Developing with AI Agents — Codex, Claude Code & Cowork Practical Guide
x5gtrn
PRO
0
1.3k
決定論的オーケストレーションの設計と実装 / Design and Implementation of Deterministic Orchestration
nrslib
4
1.4k
AIだと陥りがちなJakarta EE最新技術への移行時の落とし穴と解決策
tnagao7
0
110
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
Featured
See All Featured
Building AI with AI
inesmontani
PRO
1
1.1k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
340
The innovator’s Mindset - Leading Through an Era of Exponential Change - McGill University 2025
jdejongh
PRO
1
200
Designing Experiences People Love
moore
143
24k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
160
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
440
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
65
56k
The Illustrated Children's Guide to Kubernetes
chrisshort
51
52k
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Into the Great Unknown - MozCon
thekraken
41
2.6k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
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
͝ਗ਼ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠