說到與UIKit的集成不免會覺得有些雞肋,因為現(xiàn)在很難做到只支持iOS13,不過到iOS14時,這種集成就變得必不可少了吧,在此先預(yù)熱一下咯 ~ 先想想使用場景:
- 在現(xiàn)有基于UIKit的App中使用SwiftUI - 這應(yīng)該是最常見的一種方式;
- 在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,如何使用呢?
- 添加內(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)
}
}
- 實現(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ù)熱嘛,用到的時候再來重溫咯~