大师学SwiftUI第16章 - UIKit框架集成

简介: SwiftUI是一套新框架,因此并没有包含我们构建专业应用所需的所有工具。这意味着我们会需要求助于UIKit(移动设备)和AppKit(Mac电脑)等原生框架所提供的工具。

SwiftUI是一套新框架,因此并没有包含我们构建专业应用所需的所有工具。这意味着我们会需要求助于UIKit(移动设备)和AppKit(Mac电脑)等原生框架所提供的工具。

我们已介绍过UIKit。它是一套SwiftUI在后台实现用于创建大部分视图和控件的框架。有些UIKit类用于运行应用(UIApplication)、加载图片(UIImage)、管理设备(UIDevice)及窗口(UIWindow),而另一些定义用于设置应用和场景(UiApplicationDelegateUIWindowSceneDelegate)的代理。当然,该框架提供了我们创建界面所需的所有工具,包括两个创建和管理视图的基本类,称为UIViewUIViewController

最后面这两个类,UIViewUIViewController,是在我们需要对SwiftUI界面添加UIKit特性时需要实现的类。UIView的子类用于在屏幕上显示信息,比如标签和图像,以及创建控件,比如按钮、滑块和开关。而UIViewController类的子类用于展示视图及包含处理它们的值和与用户交互的必要功能。为了将这些工具集成到SwiftUI界面中,SwiftUI框架定义了两个协议:UIViewRepresentableUIViewControllerRepresentable

Representable视图

UIViewRepresentable协议定义了一结构体,作为UIView类或其子类所创建对象的一层封装(wrapper)。实现这一协议的结构体可表示SwiftUI界面中的一个UIKit视图。创建及管理UIKit视图,结构体必须要实现如下的方法。

  • makeUIView(context: Context):该方法创建一个UIKit视图并返回。context参数是对提供视图状态信息的UIViewRepresentableContext类型结构体的引用。
  • updateUIView(UIViewType, context: Context):该方法通过一个绑定属性使用SwiftUI界面所提供的信息更新UIKit视图。第一个参数是对UIKit视图的引用,context参数是对提供视图状态信息的UIViewRepresentableContext类型结构体的引用。
  • dismantleUIView(UIViewType, coordinator: Coordinator):该类型方法为释放视图做准备工作。第一个参数是UIKit视图的指针,coordinator参数是将值发回SwiftUI界面的对象。
  • makeCoordinator():该方法创建的对将UIKit视图的信息传回给SwiftUI界面。

要在SwiftUI接口中包含一个UIView对象(或其子类所创建对象),我们需要定义一个实现UIViewRepresentable协议的结构体,并实现上述的方法。makeUIView()updateUIView()方法是必选的。在makeUIView()方法中,我们必须创建并返回一个UIKit视图实例,updateUIView()法用于使用SwiftUI界面中所获取的值来更新视图。

下例中创建了一个蓝色背景的UIKit视图,并在SwiftUI视图中包含它。只需要用makeUIView()方法创建UIKit视图,但同时也要实现updateUIView()方法,因为协议有相应要求。

示例16-1:准备一个供SwiftUI使用的UIKit视图

struct MyCustomView: UIViewRepresentable {
    func makeUIView(context: Context) -> some UIView {
        let view = UIView()
        view.backgroundColor = UIColor(.blue)
        return view
    }
    func updateUIView(_ uiView: UIViewType, context: Context) {}
}

在每次创建新的MyCustomView结构体实例时调用makeUIView()方法。在该方法中,我们创建一个UIView视图,将背景色设置为蓝色并返回。因此,每次我们创建MyCustomView结构体实例的时候,会创建一个UIView并添加到SwiftUI实例之中,如下例所示。

示例16-2:在SwiftUI视图中显示UIKit视图

struct ContentView: View {
    var body: some View {
        VStack {
            MyCustomView()
                .frame(width: 200, height: 150)
                .padding()
            Spacer()
        }
    }
}

representable视图默认为弹性大小,但可以通过SwiftUI修饰符进行修改。本例中,我们使用frame()修饰符赋了固定的宽和高。

图16-1:SwiftUI界面中的UIKit视图

UIView对象创建一个空视图,但我们也可以实现接收其它用户输入的视图,如文本框、开关等。我们使用updateUIView()方法将SwiftUI视图中的值传递给UIKit视图。但如果想将UIKit视图中的值传递给SwiftUI界面,则需要实现makeCoordinator()方法。在这个方法中,我们必须创建一个coordinator对象实例并返回。coordinator是一个可将UIKit视图中的信息回发给Swift界面的对象,通常通过修改绑定属性实现。如何处理这些值取决于我们操作的UIkit视图的类型。例如,UITexView视图创建一个用户可输入多行文本的输入框,和SwiftUI中的TextEditor视图一样。这个视图通过调用代理方法上报改变。因此,要获取用户在UITexView中插入的文本并在SwiftUI中进行处理,我们需要创建一个实现UITextViewDelegate协议的coordinator类,并实现其方法。下例演示了如何创建一个UIViewRepresentable结构体来操作这个类。

示例16-3:对SwiftUI视图发送和接收值

import SwiftUI
struct TextView: UIViewRepresentable {
    @Binding var input: String
    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.backgroundColor = UIColor.yellow
        view.font = UIFont.systemFont(ofSize: 17)
        view.delegate = context.coordinator
        return view
    }
    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = input
    }
    func makeCoordinator() -> CoordinatorTextView {
        return CoordinatorTextView(input: $input)
    }
}
class CoordinatorTextView: NSObject, UITextViewDelegate {
    @Binding var inputCoordinator: String
    init(input: Binding<String>) {
        self._inputCoordinator = input
    }
    func textViewDidChange(_ textView: UITextView) {
        inputCoordinator = textView.text
    }
}

本示例创建了一个名为TexViewUIViewRepresentable结构体。结构体中有一个名为input@Binding属性,接收值并传递给SwiftUI视图。在TexView结构体定义的下面,我们定义了一类名为CoordinatorTextView的类。这是我们视图的coordinator,任务是将值发回给SwiftUI视图。为此,我们通过一个关联TexView结构体中定义的input属性的@Binding属性对其初始化,然后实现一个代理方法,在用户插入或删了字符时执行。该方法获取输入框中的当前文本,因此我们将其赋值给那个@Binding属性,发回到SwiftUI视图。UIViewRepresentable结构体初始化时。CoordinatorTextView对象通过makeCoordinator()方法创建,因而该结构体在一开始就可接收和发送值了。

SwiftUI界面中的视图实现和之前相同。只需对存储添加一个@State属性,将值传递给input属性。

示例16-4:对UIKit视图接收和发送值

struct ContentView: View {
    @State private var inputText: String = "Initial text"
    var body: some View {
        VStack {
            HStack {
                Text(inputText)
                Spacer()
                Button("Clear") {
                    inputText = ""
                }
            }
            TextView(input: $inputText)
        }.padding()
    }
}

以上视图定义了一个名为inputText@State属性、一个显示其值的Text视图、一个用空字符串替换当前值的按钮以及自定义TextInput视图的一个实例。该视图获取到inputText属性的指针,与representable视图中的输入属性相关联,这样便可以来回传递值 。

用户在编辑器中输入或删除字符时,representable视图调用coordinator中的textViewDidChange()方法,该方法将文本视图中的当前值赋给inputCoordinator属性,也就赋给了input属性。这意味着值在@State属性中可用 ,并且Text视图可将其显示到屏幕上。而在用户点击Clear按钮时,我们将空字符串赋值给@State属性,系统执行representable视图中的updateUIView()方法,关联@State属性的@Binding属性的值被赋给了视图,文本视图被清除。

图16-2:SwiftUI界面中的UITexView视图

✍️跟我一起做:创建一个多平台项目。根据示例16-3中的代码创建一个名为TextView.swift的文件。根据示例16-4中的代码更新ContextView视图。插入一段文本。应该会看到顶部的文本发生了变化。点击Clear按钮。会看到文本视图及顶部的视图中的文本被删除。

注意UIViewRepresentable结构体可表示任意UIKit视图。实现会取决于想使用的视图。我们会在后续的章节中看到更多的示例。要学习UIKitUiKit视图,可参阅UIKit for MasterMinds等书。

Representable视图控制器

UIViewControllerRepresentable协议定义一个结构体,作为对UIViewController类及子类所创建对象的封装。该类展示一个可包含其它视图的视图,为窗口或整个屏幕定义界面,类似SwiftUI文件中所定义的视图。实现UIViewControllerRepresentable协议的结构体可在一个SwiftUI界面中展示这些视图控制器。创建及管理这个视图控制器,结构体必须实现如下方法。

  • makeUIViewController(context: Context):该方法创建一个UIKit视图控制器并返回。context参数是UIViewControllerRepresentableContext类型结构体的一个指针,提供视图控制器状态的相关信息。
  • updateUIViewController(UIViewControllerType, context: Context):该方法使用SwiftUI界面所提供的信息更新UIKit视图控制器。第一个参数是UIKit视图控制器的指针,context参数是UIViewControllerRepresentableContext类型结构体的一个指针,提供视图控制器状态的相关信息。
  • dismantleUiViewController(UIViewControllerType, coordinator: Coordinator):该类型方法为释放视图控制器做准备。第一个参数是UIKit视图控制器的指针,coordinator参数是是将值发回SwiftUI界面的对象。
  • makeCoordinator():该方法创建将UIKit视图控制器信息传回SwiftUI界面的对象。

UIKit视图控制器由UIViewController的子类创建。文件通过File菜单创建 ,但选取UIKit子类的选项称为Cocoa Touch Class。选择该选项后,Xcode会显示一个可输入文件名称的窗口,并可选择所创建子类相应的父类。本例中我们创建了一个继承UIViewController类的DetailViewController类。

示例16-5:创建一个UIKit视图控制器

import UIKit
class DetailViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let label = UILabel()
        label.frame = CGRect(x: 20, y: 16, width: 250, height: 30)
        label.font = UIFont.systemFont(ofSize: 30)
        label.text = "Hello World!"
        view.addSubview(label)
    }
}

在初始化视图控制器类时,它创建一个表示界面的视图,将其赋值给view属性,并调用viewDidLoad()方法来告诉我们的代码视图已就绪。在这个方法中,我们可以执行所有必要的任务来初始化视图。在本例中,我们创建一个UILabel对象来在屏幕上显示文本。该对象类似SwiftUIText视图,但需要做一些配置。在我们的例子中,通过将CGRect值赋给frame属性来为其设置位置和大小,字体大小定义为30像素,赋值给要显示的文本,通过addSubview()方法将其添加给视图控制器的视图。

现在有了视图控制器,我们需要创建一个representable视图将其转为SwiftUI视图,如下例所示:

示例16-6:为UIKit视图控制器创建representable视图

import SwiftUI
struct MyViewController: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> DetailViewController {
        let controller = DetailViewController()
        return controller
    }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
    }
}

UIViewControllerRepresentable协议类似UIViewRepresentable。我们必须定义一个实现此协议的结构体,然后添加创建和更新视图所需的方法。在本例中,我们仅定义了makeUIViewController(),因为只需要创建视图控制器的实例,来在屏幕上进行显示。以下的SwiftUI视图在用户点击按钮时在NavigationStack视图中加载该视图控制器。

图16-7:通过SwiftUI视图加载一个UIKit视图控制器

struct ContentView: View {
    var body: some View {
        NavigationStack {
            VStack {
                NavigationLink("Open UIKit View", destination: {
                    MyViewController()
                }).buttonStyle(.borderedProminent)
                Spacer()
            }.padding()
        }
    }
}

以上视图包含一个加载MyViewController结构体实例的NavigationLink视图,因而可以从初始视图导航至结构体所创建的视图控制器,和常规SwiftUI视图的操作一样。

图16-3:SwiftUI界面中的UIKit视图控制器

✍️跟我一起做:选择屏幕顶部菜单中的File选项,新建一个文件。点击iOS版块中的Cocoa Touch Class图标,创建一个UIKit文件。在SubClass选项中选择UIViewController。插入名称DetailViewController并点击Next进行保存。使用示例16-5中的代码更新DetailViewController。用示例16-6中的代码创建一个名为MyViewController.swift的文件。使用示例16-7中的代码更新ContentView视图。运行应用,点击按钮打开UIKit视图控制器。

代码请见:GitHub仓库

其它相关内容请见虚拟现实(VR)/增强现实(AR)&visionOS开发学习笔记

相关文章
|
27天前
|
Java 程序员 API
Android|集成 slf4j + logback 作为日志框架
做个简单改造,统一 Android APP 和 Java 后端项目打印日志的体验。
97 1
|
2月前
|
算法 API Apache
Flink CDC:新一代实时数据集成框架
本文源自阿里云实时计算团队 Apache Flink Committer 任庆盛在 Apache Asia CommunityOverCode 2024 的分享,涵盖 Flink CDC 的概念、版本历程、内部实现及社区未来规划。Flink CDC 是一种基于数据库日志的 CDC 技术实现的数据集成框架,能高效完成全量和增量数据的实时同步。自 2020 年以来,Flink CDC 经过多次迭代,已成为功能强大的实时数据集成工具,支持多种数据库和数据湖仓系统。未来将进一步扩展生态并提升稳定性。
604 1
Flink CDC:新一代实时数据集成框架
|
1月前
|
开发框架 监控 搜索推荐
GoFly快速开发框架集成ZincSearch全文搜索引擎 - Elasticsearch轻量级替代为ZincSearch全文搜索引擎
本文介绍了在项目开发中使用ZincSearch作为全文搜索引擎的优势,包括其轻量级、易于安装和使用、资源占用低等特点,以及如何在GoFly快速开发框架中集成和使用ZincSearch,提供了详细的开发文档和实例代码,帮助开发者高效地实现搜索功能。
124 0
|
3月前
|
存储 消息中间件 前端开发
Web2py框架下的神秘力量:如何轻松集成第三方API,让你的应用不再孤单!
【8月更文挑战第31天】在开发现代Web应用时,常需集成第三方服务如支付网关、数据存储等。本文将指导你使用Web2py框架无缝接入第三方API。通过实例演示从注册获取API密钥、创建控制器、发送HTTP请求到处理响应的全过程。利用`requests`库与Web2py的内置功能,轻松实现API交互。文章详细介绍了如何编写RESTful控制器,处理API请求及响应,确保数据安全传输。通过本教程,你将学会如何高效整合第三方服务,拓展应用功能。欢迎留言交流心得与建议。
50 1
|
3月前
|
测试技术 Java Spring
Spring 框架中的测试之道:揭秘单元测试与集成测试的双重保障,你的应用真的安全了吗?
【8月更文挑战第31天】本文以问答形式深入探讨了Spring框架中的测试策略,包括单元测试与集成测试的有效编写方法,及其对提升代码质量和可靠性的重要性。通过具体示例,展示了如何使用`@MockBean`、`@SpringBootTest`等注解来进行服务和控制器的测试,同时介绍了Spring Boot提供的测试工具,如`@DataJpaTest`,以简化数据库测试流程。合理运用这些测试策略和工具,将助力开发者构建更为稳健的软件系统。
59 0
|
3月前
|
测试技术 持续交付 开发者
Xamarin 高效移动应用测试最佳实践大揭秘,从框架选择到持续集成,让你的应用质量无敌!
【8月更文挑战第31天】竞争激烈的移动应用市场,Xamarin 作为一款优秀的跨平台开发工具,提供了包括单元测试、集成测试及 UI 测试在内的全面测试方案。借助 Xamarin.UITest 框架,开发者能便捷地用 C# 编写测试案例,如登录功能测试;通过 Xamarin 模拟框架,则可在无需真实设备的情况下模拟各种环境测试应用表现;Xamarin.TestCloud 则支持在真实设备上执行自动化测试,确保应用兼容性。结合持续集成与部署策略,进一步提升测试效率与应用质量。掌握 Xamarin 的测试最佳实践,对确保应用稳定性和优化用户体验至关重要。
63 0
|
3月前
|
数据库 Java 数据库连接
Struts 2 与 Hibernate 的完美邂逅:如何无缝集成两大框架,轻松玩转高效 CRUD 操作?
【8月更文挑战第31天】本文通过具体示例介绍了如何在 Struts 2 中整合 Hibernate,实现基本的 CRUD 操作。首先创建 Maven 项目并添加相关依赖,接着配置 Hibernate 并定义实体类及其映射文件。然后创建 DAO 接口及实现类处理数据库操作,再通过 Struts 2 的 Action 类处理用户请求。最后配置 `struts.xml` 文件并创建 JSP 页面展示用户列表及编辑表单。此示例展示了如何配置和使用这两个框架,使代码更加模块化和可维护。
94 0
|
3月前
|
Java 数据库连接 数据库
强强联手!JSF 与 Hibernate 打造高效数据访问层,让你的应用如虎添翼,性能飙升!
【8月更文挑战第31天】本文通过具体示例详细介绍了如何在 JavaServer Faces (JSF) 应用程序中集成 Hibernate,实现数据访问层的最佳实践。首先,创建一个 JSF 项目并在 Eclipse 中配置支持 JSF 的服务器版本。接着,添加 JSF 和 Hibernate 依赖,并配置数据库连接池和 Hibernate 配置文件。然后,定义实体类 `User` 和 DAO 类 `UserDAO` 处理数据库操作。
61 0
|
3月前
|
缓存 前端开发 JavaScript
Angular邂逅PWA:一场关于如何利用现代Web技术栈中的明星框架与渐进式理念,共同编织出具备原生应用般丝滑体验、离线访问及桌面集成能力的未来Web应用的探索之旅
【8月更文挑战第31天】本文详细介绍如何利用Angular将传统Web应用升级为渐进式Web应用(PWA),克服后者在网络依赖、设备集成及通知功能上的局限。通过具体命令行操作与代码示例,指导读者从新建Angular项目到配置`manifest.json`和服务工作进程,最终实现离线访问、主屏添加及推送通知等功能,显著提升用户体验。适合各水平开发者学习实践。
39 0
|
3月前
|
机器学习/深度学习 PyTorch TensorFlow
NumPy 与机器学习框架的集成
【8月更文第30天】NumPy 是 Python 中用于科学计算的核心库之一,它提供了高效的多维数组对象,以及用于操作数组的大量函数。NumPy 的高效性和灵活性使其成为许多机器学习框架的基础。本文将探讨 NumPy 如何与 TensorFlow 和 PyTorch 等流行机器学习框架协同工作,并通过具体的代码示例来展示它们之间的交互。
58 0

热门文章

最新文章