前言

一般老项目都是基于 UIKit 的,跟着 SwiftUI 越来越老练,未来的趋势会趋向于运用 SwiftUI 来进行开,所以,项目的逐渐迁移至 SwiftUI 也变得有必要起来。这篇文章会展现怎么在 UIKit 项目中,接入 SwiftUI。而 SwiftUI 在有些地方支持的还不是特别好,所以或许会有需求在 SwiftUI 中再去引如 UIKit 去解决现在 SwiftUI 欠好解决的问题,比方富文本,所以也会介绍在 SwiftUI 中怎么去加载 UIKit 的视图。

UIKit 接入 SwiftUI

1.Push 到一个 SwiftUI 视图

假设有一个 UIKit 的 ViewControllerA,然后咱们新建一个 SwiftUI 的 SwiftUIViewB,然后从 A push 到 B。

首要在这儿新建一个 SwiftUIViewB

SwiftUI -SwiftUI 和 UIKit 的相互引用

然后,给 A 增加一个按钮,创建的代码就不写了,响应的办法为:

@IBAction func pushAction(_ sender: UIButton) {
 let swiftUIViewController = UIHostingController(rootView: SwiftUIViewB())
 navigationController?.pushViewController(swiftUIViewController, animated: true)
}

UIHostingController 是继承自 UIViewController 的,其实便是运用 UIHostingControllerSwiftUIViewB 给包裹起来,类似于 UIKit 的 UIViewController -> UIVIew 的方式,将 SwiftUI 的视图,当成 UIViewControllerUIView 一样来进行运用。

作用如下:

SwiftUI -SwiftUI 和 UIKit 的相互引用

2. 在 UIKit 视图中嵌套一个 SwiftUI 视图

改一下 SwiftUIViewB 的代码:

struct SwiftUIViewB: View {
  var body: some View {
    ZStack {
      Color.cyan
      Text("Hello, World!")
        .foregroundColor(Color.white)
    }
  }
}

然后在 A 中:

override func viewDidLoad() {
    super.viewDidLoad()
        let sView = SwiftUIViewB()
    let hostingController = UIHostingController(rootView: sView)
    self.addChild(hostingController)
    self.view.addSubview(hostingController.view)
    hostingController.didMove(toParent: self)
        hostingController.view.snp.makeConstraints({ make in
      make.centerX.equalTo(view)
      make.centerY.equalTo(view).offset(100)
      make.width.equalTo(200)
      make.height.equalTo(100)
    })
  }

一样的,借助于 UIHostingController,将 SwiftUI 的视图当做一个 UIKit 的 UIView 来运用。

作用如下:

SwiftUI -SwiftUI 和 UIKit 的相互引用

3.值传递

来测验一下改动 SwiftUI 视图的背景色,还是 A 和 B,咱们需求在 A 中去改动 B 的背景色。 首要,在 SwiftUIViewB 中新增一个 Color 特点:

struct SwiftUIViewB: View {
  var bColor: Color
  var body: some View {
    ZStack {
      bColor
      Text("SwiftUI Screen")
    }
  }
}

然后咱们构建一个属于 SwiftUIViewB 的控制器来办理它:

class SwiftUIViewBController: UIViewController {
  override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    hostingController = UIHostingController(rootView: SwiftUIViewB(bColor: bColor))
    super.init(nibName: nil, bundle: nil)
  }
 
  required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  override func viewDidLoad() {
    super.viewDidLoad(
    self.addChild(hostingController)
    self.view.addSubview(hostingController.view)
    hostingController.view.snp.makeConstraints { $0.edges.equalToSuperview() }
  }
  var bColor: Color = .white {
    didSet {
      update()
    }
  }
  let hostingController: UIHostingController<SwiftUIViewB>
 
  func update() {
    hostingController.rootView = SwiftUIViewB(bColor: bColor)
  }
}

然后在 A 中:

let hostingController = SwiftUIViewBController()
hostingController.bColor = .red
navigationController?.pushViewController(hostingController, animated: true)

4.监听值更改并主动改写 UI

在第三点中,咱们是在 bColordidSet 办法中,手动去改写 UI。SwiftUI 提供了当值发生改动时,UI 主动改写的方式,通过 ObservableObject,当然不止一种,咱们这儿先只说这种。

加一个自定义的数据:

class SwiftUIViewBConfig: ObservableObject {
  @Published var textColor: Color
  init (textColor: Color) {
    self.textColor = textColor
  }
}

然后改动一下 SwiftUIViewB 的代码,让它来运用这个数据:

struct SwiftUIViewB: View {
  var config: SwiftUIViewBConfig
 
  var body: some View {
    ZStack {
      Text("SwiftUI Screen")
        .foregroundColor(config.textColor)
    }
  }
}

然后在 A 控制器中进行运用,简单点直接运用 UIHostingController

class ViewController: UIViewController {
  let swiftUIBViewConfig = SwiftUIViewBConfig(textColor: .black)
 
  @IBAction func pushAction(_ sender: UIButton) {
    let hostingController = UIHostingController(rootView: SwiftUIViewB(config: swiftUIBViewConfig))
    navigationController?.pushViewController(hostingController, animated: true)
  }
  @IBAction func changeColorAction(_ sender: UIButton) {
    swiftUIBViewConfig.textColor = .blue
  }
}

B 中的文字颜色本来是黑色,在 A 中滴点击按钮,将 config.textColor 改成蓝色,看一下作用:

SwiftUI -SwiftUI 和 UIKit 的相互引用

SwiftUI 接入 UIKit

其实 SwiftUI 的支持还没有 UIKit 那么完善,而且最低到 13 系统,很多 SwiftUI 的特性其实支持的欠好,所以遇到实在是难搞定的,主张桥接 UIKit,或许直接运用 UIKit 来完成。

要桥接也很简单,使 UIViewRepresentable 协议将 UIView 包装一下,然后就能够在 SwiftUI 中运用了,怎么需求桥接 UIViewController,也有对应的 UIViewControllerRepresentable 协议。

UIViewRepresentable

这个协议有两个必须完成的办法,便是 makeUIViewupdateUIView

public protocol UIViewRepresentable : View where Self.Body == Never {
    func makeUIView(context: Self.Context) -> Self.UIViewType
    func updateUIView(_ uiView: Self.UIViewType, context: Self.Context)
}

举个简单的比如,桥接一个 UIView 并改动它的背景颜色。 首要,先处理好需求桥接的 UIView

struct UIKitView: UIViewRepresentable {
  @Binding var bColor: UIColor
  func makeUIView(context: UIViewRepresentableContext<UIKitView>) -> UIView {
    let view = UIView()
    return view
  }
  func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<UIKitView>) {
    uiView.backgroundColor = bColor
  }
}

然后在 SwiftUI 中运用它:

struct SwiftUIView: View {
  @State var kitViewColor: UIColor = .blue
  var body: some View {
    VStack {
      UIKitView(bColor: $kitViewColor)
        .frame(width: 100, height: 100)
      Button {
        kitViewColor = .red
      } label: {
        Text("change color")
      }
    }
  }
}

看一下作用:

SwiftUI -SwiftUI 和 UIKit 的相互引用

  • makeUIView 办法在其生命周期只会调用一次,在这个办法中回来一个你要在 SwiftUI 中体现的 UIView
  • updateUIView 办法会在 UIView 的状况发生变化时被调用,在生命周期内会被调用次。

在这个比如中,运用 @Binding 包装特点将 SwiftUI 中的 State 和 UIKit 的 UIKitView 绑定在一起,当 state 发生变化,就会触发 updateUIView 办法,去改动 UIKitView 的背景颜色。

现在 bColor 是从 SwiftUI 中传递到 UIKit 的,可是假如需求从 UIKit 传值到 SwiftUI 中又该怎么完成呢,答案是运用 Coordinator

SwiftUI 从 UIKit 中获取值

假如你要桥接到 SwiftUI 中的不是一个 UIView,而是一个 UITextField,而你又刚好需求完成 UITextFiled 的代理,那么你该把这个 delegate 设置成谁呢?答案是 Coordinator

UIViewRepresentable 协议中,还有一个东西:

    public protocol UIViewRepresentable : View where Self.Body == Never {
    func makeCoordinator() -> Self.Coordinator
}

直接看怎么运用:

struct CustomTextFiled: UIViewRepresentable {
  @Binding var text: String
  class Coordinator: NSObject, UITextFieldDelegate {
    @Binding var text: String
    init(text: Binding<String>) {
      _text = text
    }
    func textFieldDidChangeSelection(_ textField: UITextField) {
      text = textField.text ?? ""
    }
  }
  func makeCoordinator() -> Coordinator {
    Coordinator(text: $text)
  }
  func makeUIView(context: Context) -> UITextField {
    let textField = UITextField()
    textField.delegate = context.coordinator
    textField.backgroundColor = .cyan
    textField.frame = CGRect(x: 50, y: 28, width: 200, height: 44)
    return textField
  }
  func updateUIView(_ uiView: UITextField, context: Context) {
    uiView.text = text
  }
}

然后在 SwiftUI 中运用:

struct SwiftUIView: View {
  @State var kitViewText: String = "123"
  var body: some View {
    VStack {
      CustomTextFiled(text: $kitViewText)
        .frame(width: 300, height: 44)
      Text(kitViewText)
    }
  }
}

上述代码中,当 UITextFiledDelegatetextFielDidChangeSelection 办法触发,coordinator 或更新 text,触发 updateUIView,最终更新 UITextField

小结

UIKit 和 SwiftUI 的相互桥接,了解之后其实并不麻烦,当然 SwiftUI 有自己的学习曲线,面向未来编程的话,主张假如仅支持 iOS13 后的 App,能够考虑运用 SwiftUI 来完成一些简单页面了。