Slide 1

Slide 1 text

SwiftUIͱUIKitΛ஥ྑͤ͘͞Δ Mike Apurin / @auramagi

Slide 2

Slide 2 text

• ΏΊΈͷ iOS ΤϯδχΞ • ࣾ಺Ͱ SwiftUI ษڧձΛ։࠵͢Δ΄Ͳ SwiftUI ʹ͸·͍ͬͯΔ • Ջͷͱ͖ͳΒίʔώʔΛᔸΕ͍ͯΔ ☕ Mike Apurin ΞϓϦϯɾϛϋΠϧ auramagi auramagi

Slide 3

Slide 3 text

SwiftUIΛͱʹ͔͘࢖͍͍ͨཧ༝ • SwiftUI ͸ൃද͔Β΋͏3೥͕ܦ͔ͬͯͳΓ੒ख़͍ͯ͠Δҹ৅ • SwiftUI Ͱ͔͠࢖͑ͳ͍෦඼ɾٕज़͕͋Δ • UI ߏஙͷεϐʔυΛ্͍͛ͨ • কདྷʹඋ͑Δ • ౳ʑ

Slide 4

Slide 4 text

WWDC 2022 Platforms State of The Union ʮΞϓϦΛ࡞Δʹ͸SwiftͱSwiftUI͸࠷దͩʯ

Slide 5

Slide 5 text

Ұ୒Ͱ͸ͳ͘ڞଘ͕͍͍ • શ͘৽نͷҊ݅Ͱ͸ͳ͍ͳΒطʹ UIKit Ͱߏங͞Ε͍ͯΔ • SwiftUI Ͱ͸ख͕ಧ͔ͳ͍ͱ͜Ζ΋͋Δ • SwiftUI / UIKit ͦΕͧΕͷ௕ॴΛੜ͔͢

Slide 6

Slide 6 text

UIKitͷதͰSwiftUIΛ࢖͏

Slide 7

Slide 7 text

UIKitͷதͰSwiftUIΛ࢖͏ • UIHostingController • SwiftUI ͷ View Λදࣔ͢Δ UIViewController • UIHostingCon fi guration — 🆕 iOS 16 • UITableViewCell / UICollectionViewCell ͷ contentCon fi guration ͱͯ͠ ઃఆͯ͠ηϧ୯ҐͰ SwiftUI ͷ View Λදࣔ

Slide 8

Slide 8 text

UIHostingController — جຊ • ੜ੒ͯ͠୯ಠͰදࣔ͢Δ func showSwiftUIView() { let host = UIHostingController( rootView: Text("Hello iOSDC 2022!") ) show(host, sender: self) } 􀬂

Slide 9

Slide 9 text

UIHostingController — جຊ • ੜ੒ͯ͠୯ಠͰදࣔ͢Δ func showSwiftUIView() { let host = UIHostingController( rootView: Text("Hello iOSDC 2022!") ) show(host, sender: self) }

Slide 10

Slide 10 text

UIHostingController — جຊ • ੜ੒ͯ͠୯ಠͰදࣔ͢Δ func showSwiftUIView() { let host = UIHostingController( rootView: Text("Hello iOSDC 2022!") ) show(host, sender: self) } // ... host.rootView = Text(“ϥʔϝϯ৯΂͍ͨ")

Slide 11

Slide 11 text

UIHostingController — جຊ • UIViewController ͷࢠڙͱ͠දࣔ͢Δ let host = UIHostingController(rootView: Text("Hello iOSDC 2022!”)) host.willMove(toParent: self) addChild(host) view.addSubview(host.view) host.didMove(toParent: self) host.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ host.view.topAnchor.constraint(equalTo: view.topAnchor), host.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), host.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), host.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), ]) • ࢠVCͱͯ͠௥Ճ͓͔ͯ͠ͳ͍ͱϨΠΞ΢τͷ໰୊͕ൃੜ͢Δ৔߹͕͋Δ • Safe Area ͕ઃఆ͞Εͳ͍ • Environment ʹઃఆͨ͠஋͕൓ө͞Εͳ͍

Slide 12

Slide 12 text

UIHostingController — جຊ struct EnvironmentTestView: View { @Environment(\.colorScheme) var colorScheme var body: some View { Text(verbatim: "\(colorScheme)") .font(.title) .padding(80) } } let host = UIHostingController( rootView: VStack { EnvironmentTestView() .border(.white) Rehost { // UIViewControllerΛ௨ͯ͠SwiftUIΛදࣔ͢Δ EnvironmentTestView() .border(.black) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.blue.ignoresSafeArea()) .environment(\.colorScheme, .dark) ) host.willMove(toParent: parent) addChild(host) view.addSubview(host.view) host.didMove(toParent: parent)

Slide 13

Slide 13 text

UIHostingController — جຊ struct EnvironmentTestView: View { @Environment(\.colorScheme) var colorScheme var body: some View { Text(verbatim: "\(colorScheme)") .font(.title) .padding(80) } } let host = UIHostingController( rootView: VStack { EnvironmentTestView() .border(.white) Rehost { // UIViewControllerΛ௨ͯ͠SwiftUIΛදࣔ͢Δ EnvironmentTestView() .border(.black) } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.blue.ignoresSafeArea()) .environment(\.colorScheme, .dark) ) //host.willMove(toParent: parent) //addChild(host) view.addSubview(host.view) //host.didMove(toParent: parent) ͣΕΔ Environment ͕ؒҧ͍ͬͯΔ

Slide 14

Slide 14 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • ΠϯελϯεΛอ࣋ͯ͠ rootView Λஔ͖׵͑Δ struct MessageView: View { let message: String let action: () -> Void var body: some View { VStack { Text(message) Button("New message (SwiftUI)", action: action) } } } class HostingViewController: UIViewController { var message: String = "Not selected" { didSet { updateHostView() } } lazy var host = UIHostingController( rootView: MessageView(message: message) { [unowned self] in message = // ... } ) func updateHostView() { host.rootView = MessageView(message: message) { [unowned self] in message = // ... } } } 􀬂 􀬂

Slide 15

Slide 15 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • ΠϯελϯεΛอ࣋ͯ͠ rootView Λஔ͖׵͑Δ struct MessageView: View { let message: String let action: () -> Void var body: some View { VStack { Text(message) Button("New message (SwiftUI)", action: action) } } } class HostingViewController: UIViewController { var message: String = "Not selected" { didSet { updateHostView() } } lazy var host = UIHostingController( rootView: MessageView(message: message) { [unowned self] in message = // ... } ) func updateHostView() { host.rootView = MessageView(message: message) { [unowned self] in message = // ... } } }

Slide 16

Slide 16 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • UIViewController ͱ View Ͱڞ௨ͷ ObservableObject Λ ௨ͯ͠ঢ়ଶΛ؅ཧ͢Δ @MainActor class SharedModel: ObservableObject { @Published var message: String = "Not selected" func action() { ... } } struct MessageView: View { @ObservedObject var model: SharedModel var body: some View { VStack { Text(model.message) Button("New message (SwiftUI)", action: model.action) } } }

Slide 17

Slide 17 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • UIViewController ͱ View Ͱڞ௨ͷ ObservableObject Λ ௨ͯ͠ঢ়ଶΛ؅ཧ͢Δ class HostingViewController: UIViewController { private let model = SharedModel() private var cancellables: Set = [] override func viewDidLoad() { super.viewDidLoad() let host = UIHostingController(rootView: MessageView(model: model) ) let label = UILabel() model.$message .sink { label.text = $0 } .store(in: &cancellables) // ... } } 􀬂 􀬂

Slide 18

Slide 18 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • UIViewController ͱ View Ͱڞ௨ͷ ObservableObject Λ ௨ͯ͠ঢ়ଶΛ؅ཧ͢Δ class HostingViewController: UIViewController { private let model = SharedModel() private var cancellables: Set = [] override func viewDidLoad() { super.viewDidLoad() let host = UIHostingController(rootView: MessageView(model: model) ) let label = UILabel() model.$message .sink { label.text = $0 } .store(in: &cancellables) // ... } }

Slide 19

Slide 19 text

UIHostingController — ঢ়ଶͷ࣋ͪํ • ଞʹ΋ํ๏͸͋Γ·͢ • Combine ͷ Publisher Λ౉ͯ͠ߪಡ͢Δ struct MessageView: View { let publisher: AnyPublisher @State private var message: String = "" var body: some View { Text(message) .onReceive(publisher) { message = $0 } } } • ϓϩύςΟɾΞΫγϣϯΛ @Environment ʹೖΕΔ • ڞ௨ϞσϧΛ @EnvironmentObject Ͱઃఆ͢Δ

Slide 20

Slide 20 text

1. ਌͕ࢠ View ʹαΠζΛఏҊ 2. ࢠ View ͕ఏҊΛݩʹͳΓ͍ͨαΠζΛܾΊΔ 3. ਌͕ࣗ෼ͷதͰࢠ View Λ഑ஔ SwiftUIϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ

Slide 21

Slide 21 text

• αΠζఏҊ struct ProposedViewSize: Equatable { var width: CGFloat? var height: CGFloat? } • nil ͷͱ͖ʹ੍໿͕ͳ͍৔߹ͷαΠζΛܭࢉ • . fi xedSize() Λ͔͚ͨͱ͖΍ ScrollView ʹೖΕ ͨͱ͖ͳͲ • ϏϡʔࣗମͷαΠζ͸ͳ͍ͱ͖͸ϓϨΠεϗ ϧμʔ஋ͷ 10 ͕ฦΔ SwiftUIϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ

Slide 22

Slide 22 text

func frame( minWidth: CGFloat?, idealWidth: CGFloat?, maxWidth: CGFloat?, minHeight: CGFloat?, idealHeight: CGFloat?, maxHeight: CGFloat?, alignment: Alignment ) -> some View • min: ࠷௿஋ • ideal: ཧ૝αΠζ • max: ࠷ߴ஋ • ؔ܎Λݫक: min <= ideal <= max SwiftUIϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ

Slide 23

Slide 23 text

UIHostingController — ϨΠΞ΢τ • view.intrinsicContentSize ʹத਎ͷ SwiftUI View ͷ ideal αΠζ͕൓ө͞ΕΔ • View ʹมߋ͕͋ͬͯ΋͍ͭ΋ਖ਼͍͠஋ʹͳ͍ͬͯΔ • ͨͩ͠σϑΥϧτͰ invalidateIntrinsicContentSize() ͕ࣗಈతʹݺ͹Εͳ͍

Slide 24

Slide 24 text

UIHostingController — ϨΠΞ΢τ • 🆕 iOS 16 ͔Β͸ sizingOptions Ͱࣗಈߋ৽ͷಈ࡞ΛΧελϚΠζͰ͖Δ • .intrinsicContentSize • ࣗಈతʹ UIHostingController.view.invalidateIntrinsicContentSize() ΛݺͿ • .preferredContentSize • ࣗಈతʹ UIHostingController.preferredContentSize ʹ΋αΠζΛ൓ө • ྆ํΛಉ࣌ʹઃఆͰ͖Δ • υΩϡϝϯτʹ͸ɺॲཧίετ͕ߴ͍ͱ஫ҙ͞Ε͍ͯΔ

Slide 25

Slide 25 text

UIHostingController — ϨΠΞ΢τ class ViewController: UIViewController { func presentAsPopover() { let host = UIHostingController( rootView: Text("Hello iOSDC 2022!”).padding() ) host.preferredContentSize = host.view.intrinsicContentSize if #available(iOS 16, *) { host.sizingOptions = .preferredContentSize } host.modalPresentationStyle = .popover host.popoverPresentationController?.sourceView = view host.popoverPresentationController?.sourceRect = view.bounds host.popoverPresentationController?.delegate = self present(host, animated: true) } } extension HostingViewController: UIPopoverPresentationControllerDelegate { func adaptivePresentationStyle( for controller: UIPresentationController ) -> UIModalPresentationStyle { .none } } 􀬂

Slide 26

Slide 26 text

UIHostingController — ϨΠΞ΢τ class ViewController: UIViewController { func presentAsPopover() { let host = UIHostingController( rootView: Text("Hello iOSDC 2022!”).padding() ) host.preferredContentSize = host.view.intrinsicContentSize if #available(iOS 16, *) { host.sizingOptions = .preferredContentSize } host.modalPresentationStyle = .popover host.popoverPresentationController?.sourceView = view host.popoverPresentationController?.sourceRect = view.bounds host.popoverPresentationController?.delegate = self present(host, animated: true) } } extension HostingViewController: UIPopoverPresentationControllerDelegate { func adaptivePresentationStyle( for controller: UIPresentationController ) -> UIModalPresentationStyle { .none } }

Slide 27

Slide 27 text

UIHostingConfiguration — جຊ • 🆕 iOS 16: UITableViewCell ͋Δ͍͸ UICollectionViewCell ୯ҐͰ SwiftUI ͷ ViewΛදࣔ override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let cell = tableView.dequeueReusableCell( withIdentifier: "Cell", for: indexPath ) cell.contentConfiguration = UIHostingConfiguration { Text("Hello") } .margins(.all, 16) .background(Color.mint) return cell }

Slide 28

Slide 28 text

UIHostingConfiguration — جຊ • ηϧ͚ͩͰ͸ͳ͘୯ಠͷ UIView ͷੜ੒Ͱ΋࢖͑Δʢ͔΋ʁʣ let view = UIHostingConfiguration { ... }.makeContentView() • UIHostingController Ͱ view Λ୯ಠͨ͠৔߹ͱಈ͖͕ҧ͏ • Safe Area ͸ਖ਼͘͠൓өͤΕ͍ͯΔͬΆ͍ • தʹ͸ UIViewControllerRepresentable ͕࢖͑ͳ͍ • ύϑΥʔϚϯεʹཁ஫ҙɻreuse ͞ΕΔͱ͖ϨΠΞ΢τΛ࠶ߏங͢Δ͔Β 1/120 ඵͰ׬ྃ͢Δඞཁ͕͋Δ

Slide 29

Slide 29 text

UIHostingConfiguration — ঢ়ଶͷ࣋ͪํ • ⚠ @State / @StateObject ͷѻ͍͸ཁ஫ҙ • reuse ͞ΕͨޙͰ΋લʹදࣔ͠ηϧͷঢ়ଶ͕࢒Δ struct Cell: View { @State var i = 0 let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { Text("Cell \(indexPath.section)-\(indexPath.row). i: \(i)") Button("i += 1") { i += 1 } .buttonStyle(.borderless) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ 􀬂

Slide 30

Slide 30 text

UIHostingConfiguration — ঢ়ଶͷ࣋ͪํ • ⚠ @State / @StateObject ͷѻ͍͸ཁ஫ҙ • reuse ͞ΕͨޙͰ΋લʹදࣔ͠ηϧͷঢ়ଶ͕࢒Δ struct Cell: View { @State var i = 0 let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { Text("Cell \(indexPath.section)-\(indexPath.row). i: \(i)") Button("i += 1") { i += 1 } .buttonStyle(.borderless) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ

Slide 31

Slide 31 text

UIHostingConfiguration — ঢ়ଶͷ࣋ͪํ • ⚠ @State / @StateObject ͷѻ͍͸ཁ஫ҙ • reuse ͞ΕͨޙͰ΋લʹදࣔ͠ηϧͷঢ়ଶ͕࢒Δ struct Cell: View { @State var i = 0 let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { Text("Cell \(indexPath.section)-\(indexPath.row). i: \(i)") Button("i += 1") { i += 1 } .buttonStyle(.borderless) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ

Slide 32

Slide 32 text

UIHostingConfiguration — ঢ়ଶͷ࣋ͪํ • ৘ใݯΛηϧҎ֎ʹஔͨ͘Ίʹ ObservableObject Λ࢖͏ @MainActor class TableModel: ObservableObject { @Published var i: [IndexPath: Int] = [:] } struct Cell: View { @ObservedObject var model: TableModel let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { let i = model.i[indexPath, default: 0] Text("Cell \(indexPath.section)-\(indexPath.row). i: \(i)") Button("i += 1") { model.i[indexPath, default: 0] += 1 } .buttonStyle(.borderless) } } }

Slide 33

Slide 33 text

UIHostingConfiguration — ঢ়ଶͷ࣋ͪํ • ৘ใݯΛηϧҎ֎ʹஔͨ͘Ίʹ ObservableObject Λ࢖͏ class TableView: UITableViewController { let model = TableModel() override func tableView( _ tableView: UITableView, cellForRowAt indexPath: IndexPath ) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) cell.contentConfiguration = UIHostingConfiguration { Cell(model: model, indexPath: indexPath) } return cell } }

Slide 34

Slide 34 text

UIHostingConfiguration — ϨΠΞ΢τ • UIHostingController ͱಉ͘͡த਎ͷ SwiftUI ͷ View ͷ ideal αΠζ͕ੜ੒͞Ε ͨ view ͷ intrinsicContentSize ʹ൓ө • invalidateIntrinsicContentSize() ͸ࣗಈతʹݺ͹Εͳ͍ • 🆕 iOS 16: UITableView / UICollectionView ͷ selfSizingInvalidation ϓϩύςΟ • ηϧͷαΠζ͕ߋ৽͞Εͨ͜ͱΛݕ஌ͯࣗ͠ಈ൓ө • tableView.performBatchUpdates { } ͳͲΛ͠ͳͯ͘΋ྑ͘ͳΔ • σϑΥϧτͰ༗ޮ • SwiftUI ʹݶΒͣಉ͡࢓૊ΈͰ Auto Layout ͷ੍໿ߋ৽ΛϐοΫΞοϓͰ͖Δ

Slide 35

Slide 35 text

UIHostingConfiguration — ϨΠΞ΢τ @MainActor class SharedModel: ObservableObject { @Published var emoji: [IndexPath: [String]] = [:] } struct Cell: View { static let emojiPalette: [String] = ["🫠", ...] @ObservedObject var model: SharedModel let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { var emojiList = model.emoji[indexPath, default: []] Text("Cell \(indexPath.section)-\(indexPath.row).") Text(emojiList.joined(separator: "\n")) Button("More emoji!") { emojiList.append(Self.emojiPalette.randomElement()!) model.emoji[indexPath] = emojiList } .buttonStyle(.borderless) } } } 􀬂

Slide 36

Slide 36 text

UIHostingConfiguration — ϨΠΞ΢τ @MainActor class SharedModel: ObservableObject { @Published var emoji: [IndexPath: [String]] = [:] } struct Cell: View { static let emojiPalette: [String] = ["🫠", ...] @ObservedObject var model: SharedModel let indexPath: IndexPath var body: some View { VStack(alignment: .leading) { var emojiList = model.emoji[indexPath, default: []] Text("Cell \(indexPath.section)-\(indexPath.row).") Text(emojiList.joined(separator: "\n")) Button("More emoji!") { emojiList.append(Self.emojiPalette.randomElement()!) model.emoji[indexPath] = emojiList } .buttonStyle(.borderless) } } }

Slide 37

Slide 37 text

SwiftUIͷதͰUIKitΛ࢖͏

Slide 38

Slide 38 text

UIViewRepresentable/UIViewControllerRepresentable — جຊ • UIViewRepresentable ϓϩτίϧ • UIView Λϥοϓͯ͠ SwiftUI ͷதͰ࢖͏ • UIViewControllerRepresentable ϓϩτίϧ • UIViewController Λϥοϓͯ͠ SwiftUI ͷதͰ࢖͏

Slide 39

Slide 39 text

UIViewRepresentable/UIViewControllerRepresentable — جຊ struct UILabelView: UIViewRepresentable { let text: String func makeUIView(context: Context) -> UILabel { let view = UILabel() view.numberOfLines = 0 return view } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = text } } struct ContentView: View { var body: some View { UILabelView(text: "Hello World") } }

Slide 40

Slide 40 text

UIViewRepresentable/UIViewControllerRepresentable — جຊ • ϥΠϑαΠΫϧ ঢ়ଶߋ৽ View Tree͔Βআڈ View Treeʹ௥Ճ makeCoordinator makeUIView updateUIView dismantleUIView

Slide 41

Slide 41 text

*Representable — ঢ়ଶͷ࣋ͪํ • ҰํతͳόΠϯσΟϯά͸ී௨ͷϓϩύςΟͰͰ͖Δ struct UILabelView: UIViewRepresentable { let text: String func makeUIView(context: Context) -> UILabel { // ... } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = text } } struct ContentView: View { @State var text: String = "Hello World" var body: some View { UILabelView(text: text) .padding() } }

Slide 42

Slide 42 text

*Representable — ঢ়ଶͷ࣋ͪํ • ૒ํతͳόΠϯσΟϯάͳΒ @Binding ͳͲΛ౉͢͜ͱͰͰ͖Δ struct UISliderView: UIViewRepresentable { @Binding var value: Double let range: ClosedRange let step: Double class Coordinator { @Binding var value: Double var step: Double init(value: Binding, step: Double) { self._value = value self.step = step } @objc func updateValue(sender: UISlider) { print(step) value = round(Double(sender.value) / step) * step sender.value = Float(value) } } func makeCoordinator() -> Coordinator { Coordinator(value: $value, step: step) } }

Slide 43

Slide 43 text

*Representable — ঢ়ଶͷ࣋ͪํ • ૒ํతͳόΠϯσΟϯάͳΒ @Binding ͳͲΛ౉͢͜ͱͰ Ͱ͖Δ struct UISliderView: UIViewRepresentable { func makeUIView(context: Context) -> UISlider { let uiView = UISlider() uiView.addTarget( context.coordinator, action: #selector(Coordinator.updateValue(sender:)), for: .valueChanged ) return uiView } func updateUIView(_ uiView: UISlider, context: Context) { context.coordinator.step = step uiView.minimumValue = Float(range.lowerBound) uiView.maximumValue = Float(range.upperBound) uiView.value = Float(value) } }

Slide 44

Slide 44 text

*Representable — ঢ়ଶͷ࣋ͪํ • Coordinator ͷϓϩύςΟͱͯ͠ Representable ͷߏ଄ମΛอ࣋͢Δͱศར struct UISliderView: UIViewRepresentable { class Coordinator { var parent: UISliderView init(_ parent: UISliderView) { self.parent = parent } @objc func updateValue(sender: UISlider) { print(parent.step) parent.value = round(Double(sender.value) / parent.step) * parent.step sender.value = Float(parent.value) } } func makeCoordinator() -> Coordinator { Coordinator(self) } func updateUIView(_ uiView: UISlider, context: Context) { context.coordinator.parent = self // ... } }

Slide 45

Slide 45 text

*Representable — ঢ়ଶͷ࣋ͪํ • Environment ͳͲ SwiftUI ͷঢ়ଶ؅ཧ΋࢖͑·͢ struct UILabelView: UIViewRepresentable { @Environment(\.scenePhase) var scenePhase func makeUIView(context: Context) -> UILabel { ... } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = "\(scenePhase)" } }

Slide 46

Slide 46 text

*Representable — ঢ়ଶͷ࣋ͪํ • Environment ͳͲ SwiftUI ͷঢ়ଶ؅ཧ΋࢖͑·͢ struct UILabelView: UIViewRepresentable { @Environment(\.scenePhase) var scenePhase func makeUIView(context: Context) -> UILabel { ... } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = "\(scenePhase)" } }

Slide 47

Slide 47 text

UIViewRepresentable — ϨΠΞ΢τ • iOS 16 ͔Β͸ఏҊαΠζ͔Βࣗ෼ͷαΠζΛܭࢉ͢Δؔ਺͕௥Ճ͞Ε͍ͯΔ protocol UIViewControllerRepresentable { func sizeThatFits( _ proposal: ProposedViewSize, uiViewController: UIViewControllerType, context: Context ) -> CGSize? } protocol UIViewRepresentable { func sizeThatFits( _ proposal: ProposedViewSize, uiView: UIViewType, context: Context ) -> CGSize? } struct ProposedViewSize: Equatable { var width: CGFloat? var height: CGFloat? } ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ

Slide 48

Slide 48 text

UIViewRepresentable — ϨΠΞ΢τ • UIView ͷ systemLayoutSizeFitting ͳͲΛ࢖ͬͯ൚༻తͳαΠζܭࢉΛ࣮૷Ͱ͖Δ func sizeThatFits(_ proposal: ProposedViewSize, uiView: UIViewType, context: Context) -> CGSize? { let widthTarget = target(for: proposal.width) let heightTarget = target(for: proposal.height) return uiView.systemLayoutSizeFitting( CGSize(width: widthTarget.0, height: heightTarget.0), withHorizontalFittingPriority: widthTarget.1, verticalFittingPriority: heightTarget.1 ) } func target(for proposal: CGFloat?) -> (CGFloat, UILayoutPriority) { switch proposal { case .none: return (UIView.layoutFittingCompressedSize.width, .fittingSizeLevel) case .some(.zero): return (UIView.layoutFittingCompressedSize.width, .defaultHigh) case .some(.infinity): return (UIView.layoutFittingExpandedSize.width, .defaultLow) case let .some(value): return (value, .defaultHigh) } } • ܕ৘ใ͕͋Δ͔Βɺͦͷ UIView (UIViewController) ʹಛԽͨ͠ܭࢉ΋࣮૷Ͱ͖Δ ※ ίʔυ͸ΠϝʔδͰ͢

Slide 49

Slide 49 text

UIViewControllerRepresentable — iOS 15ҎԼͷϨΠΞ΢τ • preferredContentSize ͕ ideal ͷαΠζʹͳΔ • αΠζͷ࠷௿஋ɾ࠷ߴ஋͕ઃఆ͞Εͳ͍ͷͰجຊతʹ਌ͷఏҊαΠζͷ ··ʹͳΔ • . fi xedSize() Λ͔͚Δ͜ͱͰ preferredContentSize Ͱઃఆͨ͠஋ʹ͢Δ

Slide 50

Slide 50 text

UIViewRepresentable — iOS 15ҎԼͷϨΠΞ΢τ • AutoLayoutͷ intrinsicContentSize ͱ priority Λݩʹͯ͠αΠζΛܭࢉ Intrinsic size Compression Resistance Hugging ݁Ռ
 (min … ideal … max) (-1) - - 0 … 0 … ∞ x < 750 < 750 0 … x … ∞ x < 750 >= 750 0 … x … x x < 750 < 750 x … x … ∞ x >= 750 >= 750 x … x … x -1 = UIView.noIntrinsicMetric 750 = UILayoutPriority.defaultHigh

Slide 51

Slide 51 text

UIHostingController — جຊ struct UILabelView: UIViewRepresentable { let text: String func makeUIView(context: Context) -> UILabel { let view = UILabel() view.numberOfLines = 0 return view } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = text } } struct ContentView: View { @State var text: String = "Hello iOSDC 2022" var body: some View { UILabelView(text: text) .padding() .border(.red) } }

Slide 52

Slide 52 text

UIHostingController — جຊ struct UILabelView: UIViewRepresentable { let text: String func makeUIView(context: Context) -> UILabel { let view = UILabel() view.numberOfLines = 0 view.setContentHuggingPriority(.defaultHigh, for: .horizontal) view.setContentHuggingPriority(.defaultHigh, for: .vertical) view.setContentCompressionResistancePriority( .defaultLow, for: .horizontal ) return view } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = text } } struct ContentView: View { @State var text: String = "Hello iOSDC 2022" var body: some View { UILabelView(text: text) .padding() .border(.red) } }

Slide 53

Slide 53 text

UIViewRepresentable — iOS 15ҎԼͷϨΠΞ΢τ • σϑΥϧτͷϨΞ΢τॲཧ 1. Auto Layout ͷϓϩύςΟ͔Β UIView ͕ͳΕΔαΠζΛܾఆ 2. ͜ΕΛ΋ͱʹɺఏҊ͞ΕͨαΠζ͔ΒαΠζΛܾΊΔ 3. UIView ͷ invalidateIntrinsicContentSize() ͕ݺ͹ΕͨΒ࠶ܭࢉ

Slide 54

Slide 54 text

RepresentableKit • Auto Layout ϓϩύςΟ͔ΒαΠζΛܭࢉ͢Δ UIViewRepresentable ͷಈ࡞Λந৅Խͯ͠ UIView Λ SwiftUI Ͱදࣔ͢ΔͨΊͷϛχϥΠϒϥϦΛ࣮૷ • UIViewAdaptor ʹೖΕΔ͚ͩʂ • github.com/yumemi-inc/RepresentableKit struct UILabel_Previews: PreviewProvider { static var previews: some View { UIViewAdaptor { let view = UILabel() view.numberOfLines = 0 view.text = "To love the journey is to accept no such end. I have found, through painful experience, that the most important step a person can take is always the next one." return view } .padding() } }

Slide 55

Slide 55 text

AppϥΠϑαΠΫϧ

Slide 56

Slide 56 text

AppϥΠϑαΠΫϧ • iOS 14͔Β৽͘͠௥Ճ͞ΕͨSwiftUI޲͚ͷΞϓϦϥΠϑαΠΫϧ • ैདྷͷ UIApplicationDelegate / UISceneDelegate ͷ୅ΘΓͱͳΔ @main struct MyAwesomeApp: App { var body: some Scene { WindowGroup { Text("Hello iOSDC 2022") } } }

Slide 57

Slide 57 text

AppϥΠϑαΠΫϧ • όοΫάϥ΢ϯυʹҠߦɾ෮ؼΛݕ஌ struct MyAwesomeApp: App { @Environment(\.scenePhase) var scenePhase var body: some Scene { WindowGroup { Text("Hello iOSDC 2022") .onChange(of: scenePhase) { scenePhase in switch scenePhase { case .background: // ... case .inactive: // ... case .active: // ... @unknown default: // ... } } } } }

Slide 58

Slide 58 text

AppϥΠϑαΠΫϧ • ࠶ىಈ࣌ͷঢ়ଶ෮ݩ @SceneStorage("info") var info = "" • Deep Linkingʹ؆୯ʹରԠ WindowGroup { Text("Hello iOSDC 2022") .onOpenURL { url in // ϦϯΫλοϓɺ௨஌։෧ɺWidgetsͷλοϓͳͲ } .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in guard let url = userActivity.webpageURL else { return } // SafariϦμΠϨΫτͳͲ } }

Slide 59

Slide 59 text

UIApplicationDelegateͱซ༻ͯ͠࢖͏ • UIApplicationDelegate ͷશ෦ͷػೳʹରԠ͍ͯ͠ΔΘ͚Ͱ΋ͳ͍ • ྫ͑͹ɺϓογϡ௨஌ͷσόΠετʔΫϯΛऔಘ͢Δؔ਺૬౰ͷ΋ͷ͕ͳ͍ • application(_, didRegisterForRemoteNoti fi cationsWithDeviceToken:) • Ҿ͖ଓ͖ซ༻Ͱ͖ΔΑ͏ʹ͸ϓϩύςΟϥούʔ͕༻ҙ͞Ε͍ͯΔ @main struct MyAwesomeApp: App { @UIApplicationDelegateAdaptor var delegate: AppDelegate var body: some Scene { WindowGroup { Text("Hello iOSDC 2022") } } } class AppDelegate: NSObject, UIApplicationDelegate { ... }

Slide 60

Slide 60 text

UIApplicationDelegateͱซ༻ͯ͠࢖͏ • طଘͷ UIApplicationDelegate ͔ΒҠߦͨ͠ͱ͖ͷ஫ҙ఺ • window ͷΞΫηεͰ͖ͳ͘ͳΔ • window.makeKeyAndVisible() ͷॲཧ΋ෆཁ • UIApplication.shared.delegate ͸ҧ͏ΫϥεʹͳΔ • ࣗ෼ͷΫϥε͸ SwiftUI.AppDelegate ͱ͍͏ SwiftUI ಺෦ͷඇެ։ ϥούʔͰแ·ΕΔ͔Β

Slide 61

Slide 61 text

UISceneDelegateͱซ༻ͯ͠࢖͏ • ެࣜͷϓϩύςΟͳͲ͕༻ҙ͞Ε͍ͯͳ͍͚Ͳࣗલͷ UISceneDelegate ΋ ซ༻Ͱ͖Δ class AppDelegate: NSObject, UIApplicationDelegate { func application( _ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions ) -> UISceneConfiguration { let configuration = UISceneConfiguration( name: "MainScene", sessionRole: connectingSceneSession.role ) configuration.delegateClass = SceneDelegate.self return configuration } } class SceneDelegate: NSObject, UIWindowSceneDelegate { func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { guard let scene = scene as? UIWindowScene else { return } // ... } }

Slide 62

Slide 62 text

UISceneDelegateͱซ༻ͯ͠࢖͏ • ΧελϜͷ UISceneDelegate Λ࢖ͬͨͱ͖ͷ஫ҙ఺ • UISceneDelegate.scene(_:willConnectTo:options:) Ͱ Key Window Λ ࡞Βͳ͍ • SwiftUI ଆͰࣗಈతʹ࡞ΒΕΔ • windowScene.delegate ͸ҧ͏ΫϥεʹͳΔ • ͜Ε΋ SwiftUI.AppSceneDelegate ͱ͍͏ඇެ։ϥούΫϥεͰแ ·ΕΔ͔Β

Slide 63

Slide 63 text

iOS 13΋αϙʔτ͢Δ • iOS 14 ະຬͷରԠ͕ඞཁͳΒผͷܕΛ @main ʹͯ͠෼ذΛೖ Εͯैདྷͷ UIKit ϥΠϑαΠΫϧͱ SwiftUI ϥΠϑαΠΫϧʹ੾ Γସ͑Δ͜ͱ͕Մೳ • ίʔυ͕ଟগॏෳ͢Δ͕࠷௿ରԠόʔδϣϯΛ iOS 14 ·Ͱ্ ͛ͨͱ͖ʹ͸αΫοͱ࡟আ͢Δ͚ͩ

Slide 64

Slide 64 text

iOS 13΋αϙʔτ͢Δ @MainActor class AppContainer: NSObject, ObservableObject { @ViewBuilder var rootView: some View { ... } @available(iOS, deprecated: 14) func launch(window: UIWindow?) { window?.rootViewController = UIHostingController( rootView: rootView ) window?.makeKeyAndVisible() } } class AppDelegate: NSObject, UIApplicationDelegate { let container = AppContainer() @available(iOS, deprecated: 14) var window: UIWindow? @available(iOS, deprecated: 14) func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { if #unavailable(iOS 14) { container.launch(window: window) } return true } }

Slide 65

Slide 65 text

iOS 13΋αϙʔτ͢Δ struct MyAwesomeApp: App { @UIApplicationDelegateAdaptor var delegate: AppDelegate var body: some Scene { WindowGroup { delegate.container.rootView } } } @main @MainActor struct AppLauncher { static func main() { if #available(iOS 14, *) { MyAwesomeApp.main() } else { AppDelegate.main() } } }

Slide 66

Slide 66 text

ʢ൪֎ʣෳ਺ͷ UIApplicationDelegate ʹରԠ • ΞϓϦͷීஈͷىಈॲཧΛεΩοϓ͍ͨ͠ͱ͖ͳͲෳ਺ͷ UIApplicationDelegate ͕࣮૷͞Ε͍ͯΔ৔߹͕͋Δ • @main ʹͳΔܕΛࣗલͰ࣮૷ͯ͠ىಈ࣌ʹ੾Γସ͑Δ͜ͱ͕Մೳ @main @MainActor struct AppLauncher { static func main() { func delegateClass(_ string: String) -> UIApplicationDelegate.Type? { NSClassFromString(string) as? UIApplicationDelegate.Type } if let appDelegate = delegateClass("MyAwesomeAppTests.TestAppDelegate") { appDelegate.main() } else { MyAwesomeApp.main() } } }

Slide 67

Slide 67 text

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