SwiftUI - 與UIKit集成

說到與UIKit的集成不免會覺得有些雞肋,因為現(xiàn)在很難做到只支持iOS13,不過到iOS14時,這種集成就變得必不可少了吧,在此先預(yù)熱一下咯 ~ 先想想使用場景:

  1. 在現(xiàn)有基于UIKit的App中使用SwiftUI - 這應(yīng)該是最常見的一種方式;
  2. 在SwiftUI中使用UIKit - 新寫的頁面是用了SwiftUI,但不免會跳轉(zhuǎn)到原先的頁面,或是在SwiftUI要使用UIKit寫的一些View,畢竟重寫原先所有的view成本有些大,需要逐步替換。

UIKit中使用SwiftUI

還記得我們在SwiftUI 初識 中提到的UIHostingController,它是SwiftUI的容器,同時也是UIViewController的子類。集成方式也就顯而易見咯。

@IBAction func showSwiftUIByCode(_ sender: Any) {
  let vc = UIHostingController(rootView: ContentView())
  self.show(vc, sender: nil)
}

若使用的是Segue

@IBSegueAction func openSwiftUI(_ coder: NSCoder) -> UIViewController? {
  return UIHostingController(coder: coder, rootView: ContentView())
}

在SwiftUI中使用UIKit

首先,我們來看看,如何從SwiftUI跳轉(zhuǎn)到一個UIViewController。先寫一個最簡單的UIViewController:

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = UIColor.red
  }
}

在SwiftUI中沒有提供類似self.show(vc, sender: nil)的方式,需要通過一個wrapper把ViewController包裝一下,可以認為這就是一個適配器。SwiftUI提供了UIViewControllerRepresentable協(xié)議,來承擔(dān)適配的功能,寫一個自己的ViewControllerRepresentation實現(xiàn)這個協(xié)議。

import SwiftUI

struct ViewControllerRepresentation: UIViewControllerRepresentable {
  typealias UIViewControllerType = ViewController
    
  func makeUIViewController(context: UIViewControllerRepresentableContext<ViewControllerRepresentation>) -> ViewControllerRepresentation.UIViewControllerType {
    return UIStoryboard(name: "Storyboard", bundle: nil).instantiateViewController(identifier: "ViewController") as! ViewController
}
    
  func updateUIViewController(_ uiViewController: ViewControllerRepresentation.UIViewControllerType, context: UIViewControllerRepresentableContext<ViewControllerRepresentation>) {}
}

其中的typealias UIViewControllerType = ViewController指明了要包裹的UIViewController的具體類型,并實現(xiàn)兩個方法,一個是make,一個是update。其中make顧名思義,就是如何生成我們的ViewController,大家可以用自己喜歡的方式初始化它。update暫時放空,它的作用是如何更新這個VC。我們會在下個示例中來看看它的用法。
接下來,我們要在SwiftUI中來調(diào)用我們這里定義的ViewControllerRepresentation。

var body: some View {
  NavigationView {
    VStack {
      HStack {
        TargetView(showAlert: $showAlert, rTarget: rTarget, gTarget: gTarget, bTarget: bTarget)
        MatchingView(rGuess: $rGuess, gGuess: $gGuess, bGuess: $bGuess, counter: $counter.counter)
      }
      Button(action: { self.showAlert = true }) { Text("Hit me") }.alert(isPresented: $showAlert) { () -> Alert in
        Alert(title: Text("Your Score"),message: Text(String(computeScore())))
      }.frame(width: nil, height: 35, alignment: .center)
      SlideView(value: $rGuess, textColor: .red)
      SlideView(value: $gGuess, textColor: .green)
      SlideView(value: $bGuess, textColor: .blue)
      NavigationLink(destination: ViewControllerRepresentation()) {
        Text("Play BullsEye").frame(width: nil, height: 31, alignment: .center)
      }.padding(.bottom)
    }
  }
}

在底部我們放了一個NavigationLink,它就是在NavigationView中跳轉(zhuǎn)至其他頁面的一個鏈接容器,指明他的destination就是我們剛剛定義好的ViewControllerRepresentation,run一下,大功告成。

此時,不免會想,如何傳遞參數(shù)呢?這個UIViewController如何將一下返回值回傳給SwiftUI呢?我們用下一個例子來說明,這個例子中是在SwiftUI中放置一個UIKit定義的UIView,先看code:

import SwiftUI
import UIKit

struct ColorUISlider: UIViewRepresentable {
  var color: UIColor
  @Binding var value: Double
    
  typealias UIViewType = UISlider
    
  func makeUIView(context: UIViewRepresentableContext<ColorUISlider>) -> UISlider {
    let slider = UISlider(frame: .zero)
    slider.thumbTintColor = color
    slider.value = Float(value)
    slider.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .valueChanged)
    return slider
  }
    
  func updateUIView(_ uiView: UISlider, context: UIViewRepresentableContext<ColorUISlider>) {
    uiView.value = Float(value)
  }
    
  class Coordinator: NSObject {
    var value: Binding<Double>
    init(value: Binding<Double>) {
      self.value = value
    }
        
    @objc func valueChanged(_ sender: UISlider) {
      self.value.wrappedValue = Double(sender.value)
    }
  }
    
  func makeCoordinator() -> ColorUISlider.Coordinator {
    return Coordinator(value: $value)
  }
}

code有點多,我們一點點看。首先想用再SwiftUI中使用UIView,同樣需要一個適配器,在UIViewController時,用了UIViewControllerRepresentable,UIView提供了同樣的協(xié)議UIViewRepresentable,需要的方法也如出一轍:

typealias UIViewType = UISlider
func makeUIView(...)
func makeUIView(...)

typealias UIViewType = UISlider指定了需要包裝的UIView的類型,make方法要求返回UIView的實例,這里無需指定該view的frame或約束。

還記得我們之前留下的問題么?如何從SwiftUI中傳值給UIView(UIViewController是相同的),直接給這個ColorUISlider添加屬性,通過struct的默認構(gòu)造函數(shù),便可以將參數(shù)傳入:

ColorUISlider(color: .red, value: .constant(0.5))

那么這個ColorUISlider如何影響其父view呢?我們看到了這個@Binding參數(shù)@Binding var value: Double(一個引用類型,如果不記得了可以查閱之前的一篇文章Data Binding),但Binding是沒有辦法在UIView中直接使用的,還需另一個類,Coordinator,如何使用呢?

  1. 添加內(nèi)部類:
class Coordinator: NSObject {
  var value: Binding<Double>
  init(value: Binding<Double>) {
    self.value = value
  }
        
  @objc func valueChanged(_ sender: UISlider) {
    self.value.wrappedValue = Double(sender.value)
  }
}
  1. 實現(xiàn)UIViewRepresentable的另一個方法makeCoordinator:
func makeCoordinator() -> ColorUISlider.Coordinator {
  return Coordinator(value: $value)
}

當(dāng)我們初始化UISlider的時候,給它添加了一個行為:

slider.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged(_:)), for: .valueChanged)

在我們滑動滑塊的時候,會觸發(fā):

@objc func valueChanged(_ sender: UISlider) {
  self.value.wrappedValue = Double(sender.value)
}

從而使Binding起作用,影響外面?zhèn)魅氲?code>value,進而作用于父View。

到此為止,我們集成SwiftUI的預(yù)熱已經(jīng)結(jié)束,還有很多細節(jié)沒有涉及,比如context。畢竟是預(yù)熱嘛,用到的時候再來重溫咯~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容