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

既存アプリにSwiftUIをどう組み込んでいくか

 既存アプリにSwiftUIをどう組み込んでいくか

WWDC Extended Tokyo 2021 #wwdctokyo
https://yj-meetup.connpass.com/event/211321

0620564f0125b8b3b7f4fe40c10b8b4e?s=128

Tatsuya Tanaka

June 08, 2021
Tweet

Transcript

  1. ©︎ 2021 Yahoo Japan Corporation All rights reserved. طଘΞϓϦʹSwiftUIΛ 


    Ͳ͏૊ΈࠐΜͰ͍͔͘ ాதୡ໵ (@tattn) WWDC Extended 21 (#wwdctokyo)
  2. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ాத ୡ໵

    / ͨͳͨͭ (@tattn) • CTOࣨΞϓϦ౷ׅ෦ @tattn @tanakasan2525 @tattn
  3. ©︎ 2021 Yahoo Japan Corporation All rights reserved. WWDC21

  4. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ͦΖͦΖiOS 12ͷαϙʔτΛ੾ΔλΠϛϯά

    ྫ೥௨ΓͳΒWWDC21ͰiOS 15͕ൃද͞Εɺ 9݄ࠒʹϦϦʔε͞ΕΔ iOS 12ͷαϙʔτΛ੾ΔΞϓϦ΋গͣͭ͠૿͍͑ͯ͘
  5. ©︎ 2021 Yahoo Japan Corporation All rights reserved. OSͷਁಁ཰ iOS

    14 IUUQTEFWFMPQFSBQQMFDPNKQTVQQPSUBQQTUPSF 2021೥2݄24೔ݱࡏ iOS 13 ͦΕҎલ > iOS 14 ͸ɺաڈ4೥ؒʹಋೖ͞ΕͨσόΠεͷ86%Ͱ࢖༻͞Ε͍ͯ·͢ɻ 86% 12% 2%
  6. ©︎ 2021 Yahoo Japan Corporation All rights reserved. SwiftUIͷ࣌୅౸དྷʁ ɾiOS

    12Λ੾ΔͱSwiftUI͕࢖͑Δʂ 
 ɾଞʹ΋ศརͳAPI͕࢖͑Δ (࠷ޙͷLTͰ঺հ) طଘͷΞϓϦʹ΋SwiftUIΛऔΓೖΕ͍ͯ͘͜ͱ͕૿͑Δ 
 Ͳ͏΍ͬͯ૊ΈࠐΜͰ͍͔͕͘՝୊
  7. ©︎ 2021 Yahoo Japan Corporation All rights reserved. SwiftUIͷҠߦํ਑ ɾ͢΂ͯSwiftUIͰ࡞Γ௚ͯ͠ϦχϡʔΞϧ

    
 ɹɹˠΞϓϦͷن໛ײʹΑͬͯ͸ίετେ 
 ɹɹɹ(Ծʹͦ͏͢ΔͳΒiOS 13ͷαϙʔτΛ੾ΕΔλΠϛϯά͕͓͢͢Ί) ɾ৽نͷXib΍Storyboard͸࡞Βͳ͍ 
 ɹɾ৽نը໘͸SwiftUIͰ࡞Δ ɹɾطଘͷը໘վम࣌ʹͦͷ෦඼ΛSwiftUIͰ࡞Γ௚͢ ɹɹˠஈ֊తʹҠߦͭͭ͠ϝϦοτΛڗडͰ͖Δ
  8. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ݸਓత͓͢͢ΊͷҠߦํ਑ (iOS

    13αϙʔτ͢Δ৔߹) ɾ৽͍͠ը໘΍UI෦඼վम࣌ʹSwiftUIΛ࢖͏ 
 ɹɹɾxib΍Storyboard͸ࠓޙෛ࠴ʹͳΔ ɾSwiftUIͷը໘ભҠ͸Ͱ͖Δ͚ͩSwiftUIͷ࢓૊ΈΛ࢖͏ 
 ɹɹɾࠓޙɺSwiftUIͷ࢓૊Έʹ׬શҠߦ͢ΔίετΛݮΒ͢ 
 ɾSwiftUIͰ࣮ݱͰ͖ͳ͍࣌͸UIKitΛ"ہॴར༻"͢Δ ɾiOS 13Ͱ࢖͑ͳ͍ػೳ͸࢖͑ΔػೳͰ୅༻͢Δ
  9. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ݸਓత͓͢͢ΊͷҠߦํ਑ (iOS

    13αϙʔτ͢Δ৔߹) ɾ৽͍͠ը໘΍UI෦඼վम࣌ʹSwiftUIΛ࢖͏ 
 ɹɹɾxib΍Storyboard͸ࠓޙෛ࠴ʹͳΔ ɾSwiftUIͷը໘ભҠ͸Ͱ͖Δ͚ͩSwiftUIͷ࢓૊ΈΛ࢖͏ 
 ɹɹɾࠓޙɺSwiftUIͷ࢓૊Έʹ׬શҠߦ͢ΔίετΛݮΒ͢ 
 ɾSwiftUIͰ࣮ݱͰ͖ͳ͍࣌͸UIKitΛ"ہॴར༻"͢Δ ɾiOS 13Ͱ࢖͑ͳ͍ػೳ͸࢖͑ΔػೳͰ୅༻͢Δ
  10. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ৽نը໘͸SwiftUIͰ࡞Δ: UIKit→SwiftUI

    let hostingController = UIHostingController(rootView: NewView()) present(hostingController, animated: true, completion: nil) let hostingController = UIHostingController(rootView: NewView()) navigationController?.pushViewController(hostingController, animated: true) SwiftUIͷը໘Λpresent SwiftUIͷը໘Λpush UIKit SwiftUI let hostingController = UIHostingController(rootView: NewView()) self.addChild(hostingController) stackView.addArrangedSubview(hostingController.view) hostingController.didMove(toParent: self) UIStackViewʹSwiftUIͷUIίϯϙʔωϯτΛຒΊࠐΈ SwiftUI UIKit UIKit
  11. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ݸਓత͓͢͢ΊͷҠߦํ਑ (iOS

    13αϙʔτ͢Δ৔߹) ɾ৽͍͠ը໘΍UI෦඼վम࣌ʹSwiftUIΛ࢖͏ 
 ɹɹɾxib΍Storyboard͸ࠓޙෛ࠴ʹͳΔ ɾSwiftUIͷը໘ભҠ͸Ͱ͖Δ͚ͩSwiftUIͷ࢓૊ΈΛ࢖͏ 
 ɹɹɾࠓޙɺSwiftUIͷ࢓૊Έʹ׬શҠߦ͢ΔίετΛݮΒ͢ 
 ɾSwiftUIͰ࣮ݱͰ͖ͳ͍࣌͸UIKitΛ"ہॴར༻"͢Δ ɾiOS 13Ͱ࢖͑ͳ͍ػೳ͸࢖͑ΔػೳͰ୅༻͢Δ
  12. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ݸਓత͓͢͢ΊͷҠߦํ਑ (iOS

    13αϙʔτ͢Δ৔߹) ɾ৽͍͠ը໘΍UI෦඼վम࣌ʹSwiftUIΛ࢖͏ 
 ɹɹɾxib΍Storyboard͸ࠓޙෛ࠴ʹͳΔ ɾSwiftUIͷը໘ભҠ͸Ͱ͖Δ͚ͩSwiftUIͷ࢓૊ΈΛ࢖͏ 
 ɹɹɾࠓޙɺSwiftUIͷ࢓૊Έʹ׬શҠߦ͢ΔίετΛݮΒ͢ 
 ɾSwiftUIͰ࣮ݱͰ͖ͳ͍࣌͸UIKitΛ"ہॴར༻"͢Δ ɾiOS 13Ͱ࢖͑ͳ͍ػೳ͸࢖͑ΔػೳͰ୅༻͢Δ
  13. ©︎ 2021 Yahoo Japan Corporation All rights reserved. SwiftUIͷView֊૚ struct

    ContentView: View { var body: some View { NavigationView { Button("Hello, world") { // viewController͕΄͍͠ } } .navigationViewStyle(StackNavigationViewStyle()) } } UINavigationController
  14. ©︎ 2021 Yahoo Japan Corporation All rights reserved. SwiftUIͷView֊૚ struct

    ContentView: View { @State var viewController: UIViewController? var body: some View { NavigationView { Button("Hello, world") { // UIViewController΍UINavigationController͕औΕΔ _ = viewController _ = viewController?.navigationController } .lookUp($viewController) } .navigationViewStyle(StackNavigationViewStyle()) } } extension View { func lookUp(_ value: Binding<UIViewController?>) -> some View { LookUpViewController(content: self) { value.wrappedValue = $0 } } } struct LookUpViewController<Content: View>: UIViewControllerRepresentable { let content: Content let lookUp: (UIViewController) -> Void func makeUIViewController(context: Context) -> UIViewController { UIHostingController(rootView: content) } func updateUIViewController(_ viewController: UIViewController, context: Context) { DispatchQueue.main.async { lookUp(viewController) } } } View֊૚ʹࣗલͷViewControllerΛࠩ͠ࠐΉͱ͋Δఔ౓ࣗ༝ʹૢΕΔ ※࣮ࡍʹ࢖͏৔߹͸Stateͷແବͳߋ৽Λ΋͏গ͠཈੍͢Δͱྑ͍
  15. ©︎ 2021 Yahoo Japan Corporation All rights reserved. iOS 13༻ʹϑϧεΫϦʔϯϞʔμϧ΋࣮૷Ͱ͖Δ

    struct ContentView: View { @State var isPresented = false var body: some View { Button("Hello, world") { isPresented = true } .myFullScreenCover(isPresented: $isPresented) { NextView() } } } struct NextView: View { @Environment(\.presentationMode) var presentationMode var body: some View { Button("Dismiss") { presentationMode.wrappedValue.dismiss() } } } extension View { @ViewBuilder func myFullScreenCover<Content: View>( isPresented: Binding<Bool>, @ViewBuilder content: @escaping () -> Content) -> some View { if #available(iOS 14, *) { fullScreenCover(isPresented: isPresented, content: content) } else { FullScreenCover(isPresented: isPresented, content: self, destination: content) } } } struct FullScreenCover<Content: View, Destination: View>: View { var isPresented: Binding<Bool> let content: Content let destination: () -> Destination @State var viewController: UIViewController? = nil var body: some View { content .lookUp($viewController) .onReceive(Just(isPresented.wrappedValue)) { isPresented in if isPresented { let controller = UIHostingController(rootView: destination()) controller.modalPresentationStyle = .fullScreen viewController?.present(controller, animated: true, completion: nil) } else { viewController?.dismiss(animated: true, completion: nil) } } } } Ͱ͖Δ͚ͩUIKitײΛग़ͣ͞ɺࠓޙϐϡΞͳSwiftUIʹҠߦ͠΍͍͢I/Fʹ͢Δͱྑ͍
  16. ©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ UITableView/UICollectionViewΛ࢖ͬͯLazyԽɺCell͸SwiftUIΛ࢖͏

  17. ©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ SwiftUIͰCellͷத਎Λ࣮૷

    struct CellContent: View { let index: Int var color: Color { Color(hue: (1 + cos(Double(index * 10) * .pi / 180)) / 2, saturation: 0.5, brightness: 1) } var body: some View { VStack { Image(systemName: "swift") .padding() Text("SwiftUI: \(index)") .bold() .frame(width: 100) } .padding(4) .background(color) .padding(.horizontal, 4) } }
  18. ©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ SwiftUIΛUICollectionViewCellͰϥοϓ

    class Cell<Content: View>: UICollectionViewCell { private weak var hostingController: UIHostingController<Content>? func configure(content: Content, parent: UIViewController) { if let controller = hostingController { controller.rootView = content return } let controller = UIHostingController(rootView: content) controller.view.backgroundColor = .clear parent.addChild(controller) contentView.addSubview(controller.view) controller.didMove(toParent: parent) controller.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.topAnchor.constraint(equalTo: controller.view.topAnchor), contentView.bottomAnchor.constraint(equalTo: controller.view.bottomAnchor), contentView.leadingAnchor.constraint(equalTo: controller.view.leadingAnchor), contentView.trailingAnchor.constraint(equalTo: controller.view.trailingAnchor), ]) self.hostingController = controller } } UICollectionViewCell
  19. ©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ UICollectionViewͰSwiftUIΛϥοϓͨ͠CellΛදࣔ

    private let reuseIdentifier = "CollectionViewItem" final class CollectionViewController: UIViewController { @IBOutlet private weak var collectionView: UICollectionView! private lazy var dataSource = UICollectionViewDiffableDataSource<Int, Int>( collectionView: collectionView ) { collectionView, indexPath, item in let cell = collectionView.dequeueReusableCell( withReuseIdentifier: reuseIdentifier, for: indexPath) as! Cell<CellContent> cell.configure(content: .init(index: item), parent: self) return cell } override func viewDidLoad() { super.viewDidLoad() collectionView.register( Cell<CellContent>.self, forCellWithReuseIdentifier: reuseIdentifier ) var snapshot = NSDiffableDataSourceSnapshot<Int, Int>() snapshot.appendSections([0]) snapshot.appendItems(Array(0..<100)) dataSource.apply(snapshot) } }
  20. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ݸਓత͓͢͢ΊͷҠߦํ਑ (iOS

    13αϙʔτ͢Δ৔߹) ɾ৽͍͠ը໘΍UI෦඼վम࣌ʹSwiftUIΛ࢖͏ 
 ɹɹɾxib΍Storyboard͸ࠓޙෛ࠴ʹͳΔ ɾSwiftUIͷը໘ભҠ͸Ͱ͖Δ͚ͩSwiftUIͷ࢓૊ΈΛ࢖͏ 
 ɹɹɾࠓޙɺSwiftUIͷ࢓૊Έʹ׬શҠߦ͢ΔίετΛݮΒ͢ 
 ɾSwiftUIͰ࣮ݱͰ͖ͳ͍࣌͸UIKitΛ"ہॴར༻"͢Δ ɾiOS 13Ͱ࢖͑ͳ͍ػೳ͸࢖͑ΔػೳͰ୅༻͢Δ
  21. ©︎ 2021 Yahoo Japan Corporation All rights reserved. State /

    StateObject / ObservedObject ஋มߋ࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞Εͯ΋஋Λอ࣋ →View͕࠶ੜ੒͞Εͯ΋ӈล஋͸࠶ੜ੒͞Εͳ͍ ※1 @State @State var flag = true @StateObject @StateObject var model = ViewModel() objectWillChange࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞Εͯ΋஋Λอ࣋ →View͕࠶ੜ੒͞Εͯ΋ӈล஋͸࠶ੜ੒͞Εͳ͍ ※1 ※1 ViewͷinitͰ஋Λࢀর͍ͯ͠ΔͱView࠶ੜ੒࣌ʹӈล஋ͰॳظԽޙɺ஋͕෮ݩ͞ΕΔಈ࡞ʹͳͬͯ͠·͏ͷͰ஫ҙ @ObservedObject @ObservedObject var model = ViewModel() objectWillChange࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞ΕΔͱ஋͕ফ͑Δ 
 →View͕࠶ੜ੒͞ΕΔͱӈล஋ͷ࠶ධՁͱ୅ೖ͕ߦΘΕΔ (iOS 13+) (iOS 14+) (iOS 13+)
  22. ©︎ 2021 Yahoo Japan Corporation All rights reserved. State /

    StateObject / ObservedObject ஋มߋ࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞Εͯ΋஋Λอ࣋ →View͕࠶ੜ੒͞Εͯ΋ӈล஋͸࠶ੜ੒͞Εͳ͍ ※1 @State @State var flag = true @StateObject @StateObject var model = ViewModel() objectWillChange࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞Εͯ΋஋Λอ࣋ →View͕࠶ੜ੒͞Εͯ΋ӈล஋͸࠶ੜ੒͞Εͳ͍ ※1 ※1 ViewͷinitͰ஋Λࢀর͍ͯ͠ΔͱView࠶ੜ੒࣌ʹӈล஋ͰॳظԽޙɺ஋͕෮ݩ͞ΕΔಈ࡞ʹͳͬͯ͠·͏ͷͰ஫ҙ @ObservedObject @ObservedObject var model = ViewModel() objectWillChange࣌ʹbodyͷ࠶࣮ߦ & View͕࠶ੜ੒͞ΕΔͱ஋͕ফ͑Δ 
 →View͕࠶ੜ੒͞ΕΔͱӈล஋ͷ࠶ධՁͱ୅ೖ͕ߦΘΕΔ (iOS 13+) (iOS 14+) (iOS 13+)
  23. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ObservedObjectʹΑΔStateObjectͷ୅༻ struct

    FooView: View { @State var model = ViewModel() var body: some View { FooContentView(model: model) } } struct FooContentView: View { @ObservedObject var model: ViewModel var body: some View { Text(model.value) } } @StateͰObservableObjectΛอ࣋ @ObservedObjectͰมߋݕ஌
  24. ©︎ 2021 Yahoo Japan Corporation All rights reserved. StateObjectͷΑ͏ʹ࢖͑ΔDynamicProperty΋࡞ΕΔ @propertyWrapper

    public struct StateObservedObject<T: ObservableObject>: DynamicProperty { @State @Lazy private var object: T @ObservedObject private var updater = StateObservedObjectUpdater() @dynamicMemberLookup public struct Wrapper { let value: T let update: () -> Void public subscript<Subject>( dynamicMember keyPath: ReferenceWritableKeyPath<T, Subject> ) -> Binding<Subject> { .init( get: { value[keyPath: keyPath] }, set: { value[keyPath: keyPath] = $0 update() } ) } } public var projectedValue: Wrapper { .init(value: object, update: _updater.wrappedValue.objectWillChange.send) } public var wrappedValue: T { get { object } set { object = newValue } } public init(wrappedValue: @autoclosure @escaping () -> T) { self._object = State(wrappedValue: Lazy(wrappedValue)) } } private class StateObservedObjectUpdater: ObservableObject {} extension StateObservedObject { @propertyWrapper private class Lazy { let lazyValue: () -> T var cached: T? init(_ value: @escaping () -> T) { lazyValue = value } var wrappedValue: T { get { if let cached = cached { return cached } cached = lazyValue() return cached! } set { cached = newValue } } } } @StateObservedObject var viewModel = ViewModel()
  25. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ·ͱΊ

  26. ©︎ 2021 Yahoo Japan Corporation All rights reserved. ·ͱΊ ɾSwiftUIΛطଘͷΞϓϦʹ΋૊ΈࠐΜͰ͍ͬͯ஌ݟΛͨΊ·͠ΐ͏

    
 ɾiOS 13Λαϙʔτ͢Δ৔߹͸͔ͳΓ޻෉͕ඞཁ ɹɹϙΠϯτ͸ɹɾUIKit͸ہॴతʹ 
 ɹɹɹɹɹɹɹɹɾI/F͸iOS 14ͷAPIʹҠߦ͠΍͘͢ 
 
 ɾWWDC21ͰSwiftUI͕Ͳ͏ਐԽ͢Δָ͔͠ΈͰ͢Ͷʂʂ