Slide 1

Slide 1 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. طଘΞϓϦʹSwiftUIΛ 
 Ͳ͏૊ΈࠐΜͰ͍͔͘ ాதୡ໵ (@tattn) WWDC Extended 21 (#wwdctokyo)

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. WWDC21

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. SwiftUIͷ࣌୅౸དྷʁ ɾiOS 12Λ੾ΔͱSwiftUI͕࢖͑Δʂ 
 ɾଞʹ΋ศརͳAPI͕࢖͑Δ (࠷ޙͷLTͰ঺հ) طଘͷΞϓϦʹ΋SwiftUIΛऔΓೖΕ͍ͯ͘͜ͱ͕૿͑Δ 
 Ͳ͏΍ͬͯ૊ΈࠐΜͰ͍͔͕͘՝୊

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

©︎ 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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

©︎ 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) -> some View { LookUpViewController(content: self) { value.wrappedValue = $0 } } } struct LookUpViewController: 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ͷແବͳߋ৽Λ΋͏গ͠཈੍͢Δͱྑ͍

Slide 15

Slide 15 text

©︎ 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( isPresented: Binding, @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: View { var isPresented: Binding 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ʹ͢Δͱྑ͍

Slide 16

Slide 16 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ UITableView/UICollectionViewΛ࢖ͬͯLazyԽɺCell͸SwiftUIΛ࢖͏

Slide 17

Slide 17 text

©︎ 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) } }

Slide 18

Slide 18 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. Lazyܥͷ୅༻ SwiftUIΛUICollectionViewCellͰϥοϓ class Cell: UICollectionViewCell { private weak var hostingController: UIHostingController? 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

Slide 19

Slide 19 text

©︎ 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( collectionView: collectionView ) { collectionView, indexPath, item in let cell = collectionView.dequeueReusableCell( withReuseIdentifier: reuseIdentifier, for: indexPath) as! Cell cell.configure(content: .init(index: item), parent: self) return cell } override func viewDidLoad() { super.viewDidLoad() collectionView.register( Cell.self, forCellWithReuseIdentifier: reuseIdentifier ) var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([0]) snapshot.appendItems(Array(0..<100)) dataSource.apply(snapshot) } }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

©︎ 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+)

Slide 22

Slide 22 text

©︎ 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+)

Slide 23

Slide 23 text

©︎ 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Ͱมߋݕ஌

Slide 24

Slide 24 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. StateObjectͷΑ͏ʹ࢖͑ΔDynamicProperty΋࡞ΕΔ @propertyWrapper public struct StateObservedObject: DynamicProperty { @State @Lazy private var object: T @ObservedObject private var updater = StateObservedObjectUpdater() @dynamicMemberLookup public struct Wrapper { let value: T let update: () -> Void public subscript( dynamicMember keyPath: ReferenceWritableKeyPath ) -> Binding { .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()

Slide 25

Slide 25 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. ·ͱΊ

Slide 26

Slide 26 text

©︎ 2021 Yahoo Japan Corporation All rights reserved. ·ͱΊ ɾSwiftUIΛطଘͷΞϓϦʹ΋૊ΈࠐΜͰ͍ͬͯ஌ݟΛͨΊ·͠ΐ͏ 
 ɾiOS 13Λαϙʔτ͢Δ৔߹͸͔ͳΓ޻෉͕ඞཁ ɹɹϙΠϯτ͸ɹɾUIKit͸ہॴతʹ 
 ɹɹɹɹɹɹɹɹɾI/F͸iOS 14ͷAPIʹҠߦ͠΍͘͢ 
 
 ɾWWDC21ͰSwiftUI͕Ͳ͏ਐԽ͢Δָ͔͠ΈͰ͢Ͷʂʂ