在SwiftUI视图中包装UIViewController
SwiftUI是构建应用程序的绝佳框架,但目前还远远不够完善——它有很多事情做不到,因此,如果您想添加更多高级功能,则需要学习与UIKit交流。有时,这是为了整合您为UIKit编写的现有代码(例如,如果您为拥有现有UIKit应用程序的公司工作),但有时是因为UIKit和Apple的其他框架向我们提供了我们想要展示在SwiftUI布局中的有用代码。
在此项目中,我们将要求用户从其照片库中导入图片。UIKit随附了专用的代码来执行此操作,但尚未移植到SwiftUI,因此我们需要自己编写桥接。
在编写代码之前,您需要了解三件事,所有这些都有点像UIKit 101,但是如果您仅使用SwiftUI,它们对您来说则是全新的东西。
首先,UIKit有一个名为UIView
的类,它是布局中所有视图的父类。因此,标签,按钮,文本字段,滑块等等——都是视图。
其次,UIKit有一个名为UIViewController
的类,该类旨在保存所有代码以使视图栩栩如生。就像UIView
一样,UIViewController
具有许多子类,它们可以完成各种工作。
第三,UIKit使用一种称为委托的设计模式来确定工作在哪里进行。因此,在决定我们的代码如何响应文本字段的值更改时,我们将使用我们的功能创建一个自定义类,并让我们成为文本字段的委托。
SwiftUI使我们的代码结构非常不同,不仅如此,我们主要将结构体用于视图而非类。但是,SwiftUI将UIView
和UIViewControlle
r混合到单个View
协议中,这使我们的代码更加简单。
无论如何,所有这些都很重要,因为UIKit要求用户从其图库中选择照片的系统使用名为UIImagePickerController
的视图控制器以及名为UINavigationControllerDelegate
和UIImagePickerControllerDelegate
的委托协议。SwiftUI无法直接使用这两个,因此我们需要对其进行包装。
我们将从简单开始,逐步发展。包装UIKit视图控制器要求我们创建一个符合UIViewControllerRepresentable
协议的结构体。这是基于View
的,这意味着我们定义的结构可以在SwiftUI视图层次结构体内使用,但是我们没有提供body
属性,因为视图的主体是视图控制器本身——它仅显示UIKit传回的内容。
符合UIViewControllerRepresentable
确实需要我们实现两种方法:一种名为makeUIViewController()
,用于创建初始视图控制器,另一种名为updateUIViewController()
,其设计用于在某些SwiftUI状态更改时让我们更新视图控制器。
按Cmd + N新建一个文件,选择“Swift File”,并将其命名为ImagePicker.swift
。将import SwiftUI
添加到新文件的顶部,然后为其提供以下代码:
struct ImagePicker: UIViewControllerRepresentable {
typealias UIViewControllerType = UIImagePickerController
}
这还不足以正确编译代码,但是当Xcode显示错误消息“Type ImagePicker does not conform to protocol UIViewControllerRepresentable”时,请点击错误左侧的红色和白色圆圈,然后选择“fix”。这将使Xcode编写我们实际需要的两个方法,实际上,这些方法实际上足以让Swift找出视图控制器的类型,因此您可以删除typealias
行。
您应该具有以下结构体:
struct ImagePicker: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
code
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
code
}
}
我们将不会使用updateUIViewController()
,因此您只需删除代码(“code”)行,以使该方法为空,然后在第一个方法编写如下内容:
let picker = UIImagePickerController()
return picker
不久我们将向其中添加更多代码,但这实际上是包装基本视图控制器所需的全部。
我们的ImagePicker
结构体是有效的SwiftUI视图,这意味着我们现在可以像其他任何SwiftUI视图一样在工作表中显示它。这个特定的结构体旨在显示图像,因此我们需要一个可选的Image
视图来保存所选图像,以及确定该表是否在显示的某种状态。
将此替换为当前的ContentView
结构体:
struct ContentView: View {
@State private var image: Image?
@State private var showingImagePicker = false
var body: some View {
VStack {
image?
.resizable()
.scaledToFit()
Button("Select Image") {
self.showingImagePicker = true
}
}
.sheet(isPresented: $showingImagePicker) {
ImagePicker()
}
}
}
继续并在模拟器或真实设备上运行它。当您点击按钮时,默认的UIKit图像选择器应向上滑动,以浏览所有照片,而当您选择其中一张时,它将消失。
但是,尽管我们刚刚选择了一张图像,但在我们的视图中不会出现任何图像。您会看到,即使我们在视图中放置了SwiftUI图像,也没有将UIImagePickerController
中的选择分配给它的任何地方。
为了实现这一目标,需要一个全新的概念:coordinators。