Slide 1

Slide 1 text

SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ Mike Apurin

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

SwiftUIʹ࠷దͷ ΞʔΩςΫνϟͱ͸ Կ͔ʁ

Slide 4

Slide 4 text

MVVM? – MV? – TCA? – MVC? – MVP? – VIPER? –

Slide 5

Slide 5 text

ͦͷલʹ

Slide 6

Slide 6 text

ۜͷ஄ؙ͸ͳ͍

Slide 7

Slide 7 text

ΞʔΩςΫνϟ͸Ҋ ݅ͷχʔζΛߟྀ͢ ΂͖

Slide 8

Slide 8 text

SwiftUIҎ֎ʹUIKit͸࢖ΘΕ͍ͯΔ͔ʁ – ΞʔΩςΫνϟ͕ղܾ͍ͯ͠Δ՝୊͸χʔζ ʹϚον͍ͯ͠Δ͔ – νʔϜϝϯόʔ͸͜ͷΞʔΩςΫνϟʹ׳Ε ͍ͯΔ͔ – etc –

Slide 9

Slide 9 text

ཧ૝ͱݱ࣮͸ဃ཭͠ ͍ͯΔ

Slide 10

Slide 10 text

UIKitͱSwiftUIͷޓ ׵ੑ

Slide 11

Slide 11 text

UIKitͱSwiftUIͷޓ׵ ੑ UINavigationController ͰUIHostingControllerΛ push͢ΔΞχϝʔγϣϯ͕όά Δ let vc = UIHostingController(rootView: SwiftUIScreen()) navigationController.show(vc, sender: nil)

Slide 12

Slide 12 text

SwiftUIͷதͰNavigationLinkΛ౿Ήͱൃੜ ͠ͳ͍ – Ҋ݅ͷ౎߹্ɺUINavigationControllerΛ ࢖͏͔͠ͳ͍ – ͨͩͦ͏͢Δͱɺ৽ΊͷNavigationStack͸ UINavigationControllerͱޓ׵ੑ͕ͳ͍͔ Β NavigationLink(value:) ͸࢖͑ͳ͍ –

Slide 13

Slide 13 text

ࠓߋݹ͍ NavigationLink(destination:) ͸࢖͍ ͨ͘ͳ͍ͷͰࣗલͰ NavigationLink(value:) Λ NavigationStack͕ͳͯ͘΋࢖͑ΔΑ͏ʹόοΫ ϙʔτ͍ͯ͠·͢ github.com/auramagi/Destinations

Slide 14

Slide 14 text

SwiftPMͷػೳ

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

Development AssetsΛѻ͏ػೳ͕ͳ͍ DebugϏϧυ΍ϓϨϏϡʔʹ࢖͏ը૾΍ ϑΝΠϧ ReleaseϏϧυ͔Βࣗಈతʹഉআ͞ΕΔ

Slide 17

Slide 17 text

͍͔ͭ͘ϫʔΫΞϥ΢ϯυΛࢼ͍ͯ͠Δ Package.swiftͰ੾Γସ͑͢Δʢඇਪ঑ʣ – XcodeͷLaunch argumentͰPackage.swiftΛ੾Γସ͑Δ – ϏϧυϓϥάΠϯ – github.com/auramagi/ManageDevelopmentAssetsPlugin – खಈͰϑΥϧμΛೖΕସ͑Δ – github.com/auramagi/swiftui-first-sample –

Slide 18

Slide 18 text

ࢲ͕ٻΊ͍ͯΔ SwiftUIʹ࠷దͷ ΞʔΩςΫνϟ

Slide 19

Slide 19 text

ཁ݅ SwiftUI First – Xcode Previews͕ਏ͘ͳ͍ – Ϛϧν΢Πϯυ΢ + Ϛϧνεςʔτ͕Մೳ – UIKit, App Intents΋ߟ͑ΒΕ͍ͯΔ – ֎෦ϥΠϒϥϦͷґଘ͕ೱ͘ͳ͍ –

Slide 20

Slide 20 text

SwiftUI First SwiftUIͷঢ়ଶ؅ཧ͕࢖͑Δ – SwiftUIͷϥΠϑαΠΫϧͰ੒Γཱͬͯ ͍Δ – SwiftUIͰѻ͍΍͍͢ –

Slide 21

Slide 21 text

Previews͕ਏ͘ͳ͍ DI͕ͪΌΜͱ͍ͯ͠Δ – UIϨΠϠʔʹ͸ॏ͍ґଘΛஔ͔ͳ͍ – ϞοΫ͠΍͍͢ –

Slide 22

Slide 22 text

Ϛϧν΢Πϯυ΢Մೳ ෳ਺΢Πϯυ΢ͰͦΕͧΕϩάΠϯঢ়ଶ ͕ҧ͏ͷ͸ઃܭՄೳʁ –

Slide 23

Slide 23 text

UIKit, App Intents DI஫ೖͷ࢓ํ͸Ͳ͏ͳΔʁ – ઃܭͷॊೈੑ͕ඞཁ –

Slide 24

Slide 24 text

֎෦ϥΠϒϥϦͷґଘ ֎෦ϥΠϒϥϦΛத৺ʹΞϓϦΛઃܭͨ͠Β͍͍͔͕ ৻ॏʹݕ౼͠·͠ΐ͏ – ։ൃࢭ·ͬͯόά͕ग़ͨΒͲ͏͢Δʁ – ണ͕͢ͱ͖ʹͲΕ͚ͩେมʁ – Apple͕৽͍͠APIΛग़ͨ͠Β࠾༻ʹ๦֐͠ͳ͍͔ʁ –

Slide 25

Slide 25 text

github.com/auramagi/ swiftui-first-sample

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Naive MV ີ݁߹ – ϓϨϏϡʔແཧ – Ϗϡʔ΋ϩδοΫ΋࠶࢖༻ੑ͕௿͍ – ґଘ஫ೖ͕ߟ͑ΒΕ͍ͯͳ͍ –

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

DI஫ೖ struct AppActions { struct Products { var buy: (Product) async -> Void var refresh: () async -> Void } } ActionsΛ੾Γ෼͚Δ –

Slide 31

Slide 31 text

DI஫ೖ struct AppState { final class Products: ObservableObject { @Published var products: [Product] = [] } } StateΛ੾Γ෼͚Δ –

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

ΠχγϟϥΠβͰActionsͱState͸஫ೖՄೳʹͨ͠ ͕ɺத਎ͷ࣮૷͸มΘΔͷ͕كͳͷͰຖճຖճࢦఆ͢Δ ͷ͸໘౗ EnvironmentͰґଘΛ఻ୡͤ͞Δ – State͸ @EnvironmentObject Ͱ – Actions͸ @Environment Ͱ –

Slide 34

Slide 34 text

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 }

Slide 35

Slide 35 text

Dependency Λอ࣋͢ΔίϯςφΛ ఆٛ protocol AppContainer { var app: AppDependency { get set } } MockAppContainer ͱ LiveAppContainer Λ ४උ

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

஫ೖΛ͠΍͘͢͢Δ public protocol ViewInjectable { typealias Content = ViewDependencyModifier.Content associatedtype InjectedBody: View func inject(content: Content) -> InjectedBody } public struct ViewDependencyModifier: 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)) } }

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

Previews #Preview { ContentView() .mockContainer(.app) } #Preview { WithMockContainer(.app) { container in MainFlow(container: container) } } ( .mockContainer ͱ WithMockContainer ͷ࣮૷͸ׂѪ)

Slide 41

Slide 41 text

ίϯςφ͸FlowͰ౉͢ struct MainFlow: View { let container: Container var body: some View { ContentView() .dependency(container) } }

Slide 42

Slide 42 text

View – ͨͩͷUI෦඼ – Screen – ը໘શମΛදࣔ͢ΔUI෦඼ – Screen ಉ࢜Λ࡞੒͠ͳ͍ – Flow – DependencyContainer Λอ࣋ͯ͠஫ೖ͢Δ – Screen Λ࡞੒͢Δ – ભҠ –

Slide 43

Slide 43 text

AppͰίϯςφΛੜ੒ @main struct SampleApp: App { @State var container = LiveAppContainer( configuration: ... ) var body: some Scene { WindowGroup { MainFlow(container: container) .dependency(container) } } }

Slide 44

Slide 44 text

No content

Slide 45

Slide 45 text

͝ਗ਼ௌ͋Γ͕ͱ͏ ͍͟͝·ͨ͠