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

SwiftUIとUIKitを仲良くさせる

Avatar for Mike Apurin Mike Apurin
September 11, 2022

 SwiftUIとUIKitを仲良くさせる

Avatar for Mike Apurin

Mike Apurin

September 11, 2022
Tweet

More Decks by Mike Apurin

Other Decks in Programming

Transcript

  1. • ΏΊΈͷ iOS ΤϯδχΞ • ࣾ಺Ͱ SwiftUI ษڧձΛ։࠵͢Δ΄Ͳ SwiftUI ʹ͸·͍ͬͯΔ

    • Ջͷͱ͖ͳΒίʔώʔΛᔸΕ͍ͯΔ ☕ Mike Apurin ΞϓϦϯɾϛϋΠϧ auramagi auramagi
  2. UIKitͷதͰSwiftUIΛ࢖͏ • UIHostingController • SwiftUI ͷ View Λදࣔ͢Δ UIViewController •

    UIHostingCon fi guration — 🆕 iOS 16 • UITableViewCell / UICollectionViewCell ͷ contentCon fi guration ͱͯ͠ ઃఆͯ͠ηϧ୯ҐͰ SwiftUI ͷ View Λදࣔ
  3. UIHostingController — جຊ • ੜ੒ͯ͠୯ಠͰදࣔ͢Δ func showSwiftUIView() { let host

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

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

    = UIHostingController( rootView: Text("Hello iOSDC 2022!") ) show(host, sender: self) } // ... host.rootView = Text(“ϥʔϝϯ৯΂͍ͨ")
  6. 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 ʹઃఆͨ͠஋͕൓ө͞Εͳ͍
  7. 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)
  8. 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 ͕ؒҧ͍ͬͯΔ
  9. 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 = // ... } } } 􀬂 􀬂
  10. 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 = // ... } } }
  11. 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) } } }
  12. UIHostingController — ঢ়ଶͷ࣋ͪํ • UIViewController ͱ View Ͱڞ௨ͷ ObservableObject Λ

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

    ௨ͯ͠ঢ়ଶΛ؅ཧ͢Δ class HostingViewController: UIViewController { private let model = SharedModel() private var cancellables: Set<AnyCancellable> = [] override func viewDidLoad() { super.viewDidLoad() let host = UIHostingController(rootView: MessageView(model: model) ) let label = UILabel() model.$message .sink { label.text = $0 } .store(in: &cancellables) // ... } }
  14. UIHostingController — ঢ়ଶͷ࣋ͪํ • ଞʹ΋ํ๏͸͋Γ·͢ • Combine ͷ Publisher Λ౉ͯ͠ߪಡ͢Δ

    struct MessageView: View { let publisher: AnyPublisher<String, Never> @State private var message: String = "" var body: some View { Text(message) .onReceive(publisher) { message = $0 } } } • ϓϩύςΟɾΞΫγϣϯΛ @Environment ʹೖΕΔ • ڞ௨ϞσϧΛ @EnvironmentObject Ͱઃఆ͢Δ
  15. 1. ਌͕ࢠ View ʹαΠζΛఏҊ 2. ࢠ View ͕ఏҊΛݩʹͳΓ͍ͨαΠζΛܾΊΔ 3. ਌͕ࣗ෼ͷதͰࢠ

    View Λ഑ஔ SwiftUIϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ
  16. • αΠζఏҊ struct ProposedViewSize: Equatable { var width: CGFloat? var

    height: CGFloat? } • nil ͷͱ͖ʹ੍໿͕ͳ͍৔߹ͷαΠζΛܭࢉ • . fi xedSize() Λ͔͚ͨͱ͖΍ ScrollView ʹೖΕ ͨͱ͖ͳͲ • ϏϡʔࣗମͷαΠζ͸ͳ͍ͱ͖͸ϓϨΠεϗ ϧμʔ஋ͷ 10 ͕ฦΔ SwiftUIϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ
  17. 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ϨΠΞ΢τॲཧͷ͓͞Β͍ ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ
  18. UIHostingController — ϨΠΞ΢τ • view.intrinsicContentSize ʹத਎ͷ SwiftUI View ͷ ideal

    αΠζ͕൓ө͞ΕΔ • View ʹมߋ͕͋ͬͯ΋͍ͭ΋ਖ਼͍͠஋ʹͳ͍ͬͯΔ • ͨͩ͠σϑΥϧτͰ invalidateIntrinsicContentSize() ͕ࣗಈతʹݺ͹Εͳ͍
  19. UIHostingController — ϨΠΞ΢τ • 🆕 iOS 16 ͔Β͸ sizingOptions Ͱࣗಈߋ৽ͷಈ࡞ΛΧελϚΠζͰ͖Δ

    • .intrinsicContentSize • ࣗಈతʹ UIHostingController.view.invalidateIntrinsicContentSize() ΛݺͿ • .preferredContentSize • ࣗಈతʹ UIHostingController.preferredContentSize ʹ΋αΠζΛ൓ө • ྆ํΛಉ࣌ʹઃఆͰ͖Δ • υΩϡϝϯτʹ͸ɺॲཧίετ͕ߴ͍ͱ஫ҙ͞Ε͍ͯΔ
  20. 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 } } 􀬂
  21. 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 } }
  22. 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 }
  23. UIHostingConfiguration — جຊ • ηϧ͚ͩͰ͸ͳ͘୯ಠͷ UIView ͷੜ੒Ͱ΋࢖͑Δʢ͔΋ʁʣ let view =

    UIHostingConfiguration { ... }.makeContentView() • UIHostingController Ͱ view Λ୯ಠͨ͠৔߹ͱಈ͖͕ҧ͏ • Safe Area ͸ਖ਼͘͠൓өͤΕ͍ͯΔͬΆ͍ • தʹ͸ UIViewControllerRepresentable ͕࢖͑ͳ͍ • ύϑΥʔϚϯεʹཁ஫ҙɻreuse ͞ΕΔͱ͖ϨΠΞ΢τΛ࠶ߏங͢Δ͔Β 1/120 ඵͰ׬ྃ͢Δඞཁ͕͋Δ
  24. 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) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ 􀬂
  25. 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) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ
  26. 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) } } } • جຊతʹ৘ใݯ͸ηϧҎ֎ʹஔ͘ඞཁ͕͋Δ
  27. 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) } } }
  28. 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 } }
  29. UIHostingConfiguration — ϨΠΞ΢τ • UIHostingController ͱಉ͘͡த਎ͷ SwiftUI ͷ View ͷ

    ideal αΠζ͕ੜ੒͞Ε ͨ view ͷ intrinsicContentSize ʹ൓ө • invalidateIntrinsicContentSize() ͸ࣗಈతʹݺ͹Εͳ͍ • 🆕 iOS 16: UITableView / UICollectionView ͷ selfSizingInvalidation ϓϩύςΟ • ηϧͷαΠζ͕ߋ৽͞Εͨ͜ͱΛݕ஌ͯࣗ͠ಈ൓ө • tableView.performBatchUpdates { } ͳͲΛ͠ͳͯ͘΋ྑ͘ͳΔ • σϑΥϧτͰ༗ޮ • SwiftUI ʹݶΒͣಉ͡࢓૊ΈͰ Auto Layout ͷ੍໿ߋ৽ΛϐοΫΞοϓͰ͖Δ
  30. 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) } } } 􀬂
  31. 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) } } }
  32. UIViewRepresentable/UIViewControllerRepresentable — جຊ • UIViewRepresentable ϓϩτίϧ • UIView Λϥοϓͯ͠ SwiftUI

    ͷதͰ࢖͏ • UIViewControllerRepresentable ϓϩτίϧ • UIViewController Λϥοϓͯ͠ SwiftUI ͷதͰ࢖͏
  33. 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") } }
  34. *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() } }
  35. *Representable — ঢ়ଶͷ࣋ͪํ • ૒ํతͳόΠϯσΟϯάͳΒ @Binding ͳͲΛ౉͢͜ͱͰͰ͖Δ struct UISliderView: UIViewRepresentable

    { @Binding var value: Double let range: ClosedRange<Double> let step: Double class Coordinator { @Binding var value: Double var step: Double init(value: Binding<Double>, 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) } }
  36. *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) } }
  37. *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 // ... } }
  38. *Representable — ঢ়ଶͷ࣋ͪํ • Environment ͳͲ SwiftUI ͷঢ়ଶ؅ཧ΋࢖͑·͢ struct UILabelView:

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

    UIViewRepresentable { @Environment(\.scenePhase) var scenePhase func makeUIView(context: Context) -> UILabel { ... } func updateUIView(_ uiView: UILabel, context: Context) { uiView.text = "\(scenePhase)" } }
  40. 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? } ࢠ ਌ αΠζఏҊ ͳΓ͍ͨαΠζ
  41. 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) ʹಛԽͨ͠ܭࢉ΋࣮૷Ͱ͖Δ ※ ίʔυ͸ΠϝʔδͰ͢
  42. UIViewControllerRepresentable — iOS 15ҎԼͷϨΠΞ΢τ • preferredContentSize ͕ ideal ͷαΠζʹͳΔ •

    αΠζͷ࠷௿஋ɾ࠷ߴ஋͕ઃఆ͞Εͳ͍ͷͰجຊతʹ਌ͷఏҊαΠζͷ ··ʹͳΔ • . fi xedSize() Λ͔͚Δ͜ͱͰ preferredContentSize Ͱઃఆͨ͠஋ʹ͢Δ
  43. 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
  44. 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) } }
  45. 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) } }
  46. UIViewRepresentable — iOS 15ҎԼͷϨΠΞ΢τ • σϑΥϧτͷϨΞ΢τॲཧ 1. Auto Layout ͷϓϩύςΟ͔Β

    UIView ͕ͳΕΔαΠζΛܾఆ 2. ͜ΕΛ΋ͱʹɺఏҊ͞ΕͨαΠζ͔ΒαΠζΛܾΊΔ 3. UIView ͷ invalidateIntrinsicContentSize() ͕ݺ͹ΕͨΒ࠶ܭࢉ
  47. 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() } }
  48. AppϥΠϑαΠΫϧ • iOS 14͔Β৽͘͠௥Ճ͞ΕͨSwiftUI޲͚ͷΞϓϦϥΠϑαΠΫϧ • ैདྷͷ UIApplicationDelegate / UISceneDelegate ͷ୅ΘΓͱͳΔ

    @main struct MyAwesomeApp: App { var body: some Scene { WindowGroup { Text("Hello iOSDC 2022") } } }
  49. 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: // ... } } } } }
  50. 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ϦμΠϨΫτͳͲ } }
  51. 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 { ... }
  52. UIApplicationDelegateͱซ༻ͯ͠࢖͏ • طଘͷ UIApplicationDelegate ͔ΒҠߦͨ͠ͱ͖ͷ஫ҙ఺ • window ͷΞΫηεͰ͖ͳ͘ͳΔ • window.makeKeyAndVisible()

    ͷॲཧ΋ෆཁ • UIApplication.shared.delegate ͸ҧ͏ΫϥεʹͳΔ • ࣗ෼ͷΫϥε͸ SwiftUI.AppDelegate ͱ͍͏ SwiftUI ಺෦ͷඇެ։ ϥούʔͰแ·ΕΔ͔Β
  53. 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 } // ... } }
  54. UISceneDelegateͱซ༻ͯ͠࢖͏ • ΧελϜͷ UISceneDelegate Λ࢖ͬͨͱ͖ͷ஫ҙ఺ • UISceneDelegate.scene(_:willConnectTo:options:) Ͱ Key Window

    Λ ࡞Βͳ͍ • SwiftUI ଆͰࣗಈతʹ࡞ΒΕΔ • windowScene.delegate ͸ҧ͏ΫϥεʹͳΔ • ͜Ε΋ SwiftUI.AppSceneDelegate ͱ͍͏ඇެ։ϥούΫϥεͰแ ·ΕΔ͔Β
  55. iOS 13΋αϙʔτ͢Δ • iOS 14 ະຬͷରԠ͕ඞཁͳΒผͷܕΛ @main ʹͯ͠෼ذΛೖ Εͯैདྷͷ UIKit

    ϥΠϑαΠΫϧͱ SwiftUI ϥΠϑαΠΫϧʹ੾ Γସ͑Δ͜ͱ͕Մೳ • ίʔυ͕ଟগॏෳ͢Δ͕࠷௿ରԠόʔδϣϯΛ iOS 14 ·Ͱ্ ͛ͨͱ͖ʹ͸αΫοͱ࡟আ͢Δ͚ͩ
  56. 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 } }
  57. 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() } } }
  58. ʢ൪֎ʣෳ਺ͷ 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() } } }