与UIKit交互

SwiftUI可以在苹果全平台上无缝兼容现有的UI框架。例如,可以在SwiftUI视图中嵌入UIKit视图UIKit视图控制器,反过来在UIKit视图UIKit视图控制器中也可以嵌入SwiftUI视图。

本篇教程展示如何把landmark应用的主页混合使用UIPageViewControllerUIPageControl。使用UIPageViewController来展示由SwiftUI视图构成的轮播图,使用状态变量和绑定来操作用户界面数据的更新。

跟着教程一步步走,可以下载工程文件进行实践。


第一节 创建一个用来展示UIPageViewController的SwiftUI视图

为了在SwiftUI视图中展示UIKit视图和UIKit视图控制器,需要创建遵循UIViewRepresentableUIViewControllerRepresentable协议的类型。创建的自定义视图类型,用来创建和配置所要展示的UIKit类型,SwiftUI框架来管理UIKIt类型的生命周期并在适当的时机更新它们。

section 1

步骤1 创建一个新的SwiftUI视图文件,命名为PageViewController.swift,并且声明PageViewController类型遵循UIViewControllerRepresentable。这个页面视图控制器存放一个UIViewController实例数组,数组中的每一个元素代表在地标滚动过程中的一页视图。

section 1 step 1

下一步添加UIViewControllerRepresentable协议的两个实现, 目前因为协议方法没有完成实现,会有报错提示。

步骤2 添加一个makeUIViewController(context:)方法,方法内部以指定的配置创建一个UIPageViewControllerSwiftUI会在准备显示视图时调用一次makeUIViewController(context:)方法创建UIViewController实例,并管理它的生命周期。

section 1 step 2

由于还缺少一个协议方法没有实现,所以目前还是会报错。

步骤3 添加updateUIViewController(_:context:)方法,这个方法里调用setViewControllers(_:direction:animated:)方法展示数组中的第一个视图控制器

section 1 step 3

创建另一个SwiftUI视图展示遵循UIViewControllerRepresentable协议的视图

步骤4 创建一个名为PageView.swift的视图,声明一个PageViewController作为子视图。初始化时使用一个视图数组来初始化,并把每一个视图都嵌入在一个UIHostingController中。UIHostingController是一个UIViewController的子类,用来在UIKit环境中表示一个SwiftUI视图。

section 1 step 4

步骤5 更新预览视图,并传入视图数组,预览视图就会开始工作了

section 1 step 5

步骤6 在继续下面的步骤前,先把PageView的预览视图固定住,以避免在文件切换时不能实现预览到PageView的改变。

section 1 step 6

第二节 创建视图控制器的数据源

短短几个步骤就做了很多事,PageViewController使用UIPageViewController去展示来自SwiftUI内容。现在是时候添加挥动手势进行页面之间的翻动了。

section 2

一个展示UIKit视图控制器的SwiftUI视图可以定义一个Coordinator类型,这个Coordinator类型由SwitUI管理,用来作为视图展示的环境

步骤1PageViewControlelr中定义一个嵌套类型CoordiantorSwiftUI管理UIViewController Representable类型的coordinator,并在调用方法时把它作为环境的一部分。

section 2 step 1

步骤2PageView Controller中添加另一个方法,创建coordinatorSwiftUI在调用makeUIViewController(context:)前会先调用makeCoordinator()方法,因此在配置视图控制器时是可以访问到coordiantor对象的。可以使用coordinator为实现通用的Cocoa模式,例如:代理模式数据源以及目标-动作

section 2 step 2

步骤3Coordinator类型添加UIPageViewControllerDataSource协议遵循,并且实现两个必要方法。这两个必要方法会建立起视图控制器之间的联系,因此可以实现页面之前的前后切换。

section 2 step 3

步骤4coordiantor作为UIPageViewController的数据源

section 2 step 4

步骤5 打开实时预览,并测试一下前后页面切换的功能是否正常

swipe landmarks

第三节 在SwiftUI视图的状态下跟踪页面

如果要添加一个自定义的UIPageControl控件,就需要一种方式能够在PageView中跟踪当前展示的页面。这就需要在PageView中声明一个@State属性,并传递一个针对该属性的绑定关系给PageViewController视图,在PageViewController中通过绑定关系更新状态属性,来反映当前展示的页面。

section 3

步骤1PageViewController中添加一个绑定属性currentPage。除了使用关键字@Binding声明属性为绑定属性外,还需要更新一下函数setViewControllers(_:direction:animated:),给它传入currentPage绑定属性

section 3 step 1

做到这一步还不能正常运行,继续进行下一步。

步骤2PageView中声明@State变量,并在创建PageViewController时把绑定属性传入。注意使用$语法创建一个针对状态变量的绑定关系。

section 3 step 2

步骤3 通过改变PageView视图中的currentPage初始值来测试绑定关系是否正常生效。也可以做一个测试按钮,点击按钮时让第二个页面展示出来

section 3 step 3

步骤4 添加一个TextView控件来展示状态变量currentPage的值,拖动页面切换时观察TextView上的值,目前不会发生变化。因为PageViewController内部没有在切换页面的过程中更新currentPage的值。

section 3 step 4

步骤5PageViewController.swift中让coordinator作为UIPageViewController的代理,并添加pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted completed: Bool) 方法。因为SwiftUI在页面切换动画完成时会调用这个方法,这样就可以这个方法内部获取当前正在展示的页面的下标,并同时更新绑定属性currentPage的值。

section 3 step 5

步骤6 coordinator除了是UIPageViewController数据源外,再把它赋值为UIPageViewController的代理。由于绑定关系是双向的,所以当页面切换时,PageView视图上的Text就会实时展示当前的页码。

section 3 step 6

section 3 step 6 gif

第四节 添加一个自定义PageControl

我们已经为包裹在UIViewRepresentable视图中的子视图上添加了一个自定义UIPageControl

section 4

步骤1 创建一个新的SwiftUI视图,命名为PageControl.swift,并使用PageControl类型遵循UIViewRepresentable协议。UIViewRepresentableUIViewControllerRepresentable类型有相同的生命周期,在UIKit类型中都有对应的生命周期方法。

section 4 step 1

步骤2PageView中用PageControl替换Text,并把VStack换成ZStack。因为总页数和当前页面都已经传入PageControl,所以PageControl已经可以正确的显示。

section 4 step 2

下一步要处理PageControl与用户的交互,让它可以被用户点击任意一边进行页面间的切换。

步骤3PageControl中创建一个嵌套类型Coordiantor,添加一个makeCoordinator()方法创建并返回一个coordinator实例。因为UIControl子类(包括UIPageControl)使用Target-Action模式,Coordinator实现一个@objc方法来更新currentPage绑定属性的值。

section 4 step 3

步骤4coordinator作为PageControl值改变事件的目标处理器,并指定updateCurrentPage(sender:)方法为处理函数

section 4 step 4

步骤5 现在就可以尝试PageControl的各种交互来切换页面,PageView展示了SwiftUIUIKit视图如何混合使用。

section 4 step 5 gif

检查是否理解

问题1 下面哪个协议可以用来把UIKit中的视图控件器桥接进SwiftUI

  • UIViewRepresentable
  • UIHostingController
  • UIViewControllerRepresentable

问题2 对于UIViewControllerRepresentable类型,下面哪个方法可以为它创建一个代理或数据源?

  • makeUIViewController(context:)方法中创建UIViewController实例的地方
  • UIViewControllerRepresentable类型的初始化器中
  • makeCoordinator()方法中