SwiftUI
可以在苹果全平台上无缝兼容现有的UI框架。例如,可以在SwiftUI视图
中嵌入UIKit视图
或UIKit视图控制器
,反过来在UIKit视图
或UIKit视图控制器
中也可以嵌入SwiftUI
视图。
本篇教程展示如何把landmark
应用的主页混合使用UIPageViewController
和UIPageControl
。使用UIPageViewController
来展示由SwiftUI
视图构成的轮播图,使用状态变量和绑定来操作用户界面数据的更新。
跟着教程一步步走,可以下载工程文件进行实践。
为了在SwiftUI
视图中展示UIKit
视图和UIKit
视图控制器,需要创建遵循UIViewRepresentable
和UIViewControllerRepresentable
协议的类型。创建的自定义视图类型,用来创建和配置所要展示的UIKit
类型,SwiftUI
框架来管理UIKIt
类型的生命周期并在适当的时机更新它们。
步骤1 创建一个新的SwiftUI
视图文件,命名为PageViewController.swift
,并且声明PageViewController
类型遵循UIViewControllerRepresentable
。这个页面视图控制器存放一个UIViewController
实例数组,数组中的每一个元素代表在地标滚动过程中的一页视图。
下一步添加UIViewControllerRepresentable
协议的两个实现, 目前因为协议方法没有完成实现,会有报错提示。
步骤2 添加一个makeUIViewController(context:)
方法,方法内部以指定的配置创建一个UIPageViewController
。SwiftUI
会在准备显示视图时调用一次makeUIViewController(context:)
方法创建UIViewController
实例,并管理它的生命周期。
由于还缺少一个协议方法没有实现,所以目前还是会报错。
步骤3 添加updateUIViewController(_:context:)
方法,这个方法里调用setViewControllers(_:direction:animated:)
方法展示数组中的第一个视图控制器
创建另一个SwiftUI
视图展示遵循UIViewControllerRepresentable
协议的视图
步骤4 创建一个名为PageView.swift
的视图,声明一个PageViewController
作为子视图。初始化时使用一个视图数组来初始化,并把每一个视图都嵌入在一个UIHostingController
中。UIHostingController
是一个UIViewController
的子类,用来在UIKit
环境中表示一个SwiftUI
视图。
步骤5 更新预览视图,并传入视图数组,预览视图就会开始工作了
步骤6 在继续下面的步骤前,先把PageView
的预览视图固定住,以避免在文件切换时不能实现预览到PageView
的改变。
短短几个步骤就做了很多事,PageViewController
使用UIPageViewController
去展示来自SwiftUI
内容。现在是时候添加挥动手势进行页面之间的翻动了。
一个展示UIKit
视图控制器的SwiftUI
视图可以定义一个Coordinator
类型,这个Coordinator
类型由SwitUI管理,用来作为视图展示的环境
步骤1 在PageViewControlelr
中定义一个嵌套类型Coordiantor
。SwiftUI
管理UIViewController Representable
类型的coordinator
,并在调用方法时把它作为环境的一部分。
步骤2 在PageView Controller
中添加另一个方法,创建coordinator
。SwiftUI
在调用makeUIViewController(context:)
前会先调用makeCoordinator()
方法,因此在配置视图控制器时是可以访问到coordiantor
对象的。可以使用coordinator
为实现通用的Cocoa模式
,例如:代理模式
、数据源
以及目标-动作
。
步骤3 让Coordinator
类型添加UIPageViewControllerDataSource
协议遵循,并且实现两个必要方法。这两个必要方法会建立起视图控制器之间的联系,因此可以实现页面之前的前后切换。
步骤4 把coordiantor
作为UIPageViewController
的数据源
步骤5 打开实时预览,并测试一下前后页面切换的功能是否正常
SwiftUI
视图的状态下跟踪页面如果要添加一个自定义的UIPageControl
控件,就需要一种方式能够在PageView
中跟踪当前展示的页面。这就需要在PageView
中声明一个@State
属性,并传递一个针对该属性的绑定关系给PageViewController
视图,在PageViewController
中通过绑定关系更新状态属性,来反映当前展示的页面。
步骤1 在PageViewController
中添加一个绑定属性currentPage
。除了使用关键字@Binding
声明属性为绑定属性外,还需要更新一下函数setViewControllers(_:direction:animated:)
,给它传入currentPage
绑定属性
做到这一步还不能正常运行,继续进行下一步。
步骤2 在PageView
中声明@State
变量,并在创建PageViewController
时把绑定属性传入。注意使用$
语法创建一个针对状态变量的绑定关系。
步骤3 通过改变PageView
视图中的currentPage
初始值来测试绑定关系是否正常生效。也可以做一个测试按钮,点击按钮时让第二个页面展示出来
步骤4 添加一个TextView
控件来展示状态变量currentPage
的值,拖动页面切换时观察TextView
上的值,目前不会发生变化。因为PageViewController
内部没有在切换页面的过程中更新currentPage
的值。
步骤5 在PageViewController.swift
中让coordinator
作为UIPageViewController
的代理,并添加pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted completed: Bool)
方法。因为SwiftUI
在页面切换动画完成时会调用这个方法,这样就可以这个方法内部获取当前正在展示的页面的下标,并同时更新绑定属性currentPage
的值。
步骤6 coordinator
除了是UIPageViewController
数据源外,再把它赋值为UIPageViewController
的代理。由于绑定关系是双向的,所以当页面切换时,PageView
视图上的Text
就会实时展示当前的页码。
PageControl
我们已经为包裹在UIViewRepresentable
视图中的子视图上添加了一个自定义UIPageControl
步骤1 创建一个新的SwiftUI
视图,命名为PageControl.swift
,并使用PageControl
类型遵循UIViewRepresentable
协议。UIViewRepresentable
和UIViewControllerRepresentable
类型有相同的生命周期,在UIKit类型中都有对应的生命周期方法。
步骤2 在PageView
中用PageControl
替换Text
,并把VStack
换成ZStack
。因为总页数和当前页面都已经传入PageControl
,所以PageControl
已经可以正确的显示。
下一步要处理PageControl
与用户的交互,让它可以被用户点击任意一边进行页面间的切换。
步骤3 在PageControl
中创建一个嵌套类型Coordiantor
,添加一个makeCoordinator()
方法创建并返回一个coordinator
实例。因为UIControl
子类(包括UIPageControl
)使用Target-Action
模式,Coordinator
实现一个@objc
方法来更新currentPage
绑定属性的值。
步骤4 把coordinator
作为PageControl
值改变事件的目标处理器,并指定updateCurrentPage(sender:)
方法为处理函数
步骤5 现在就可以尝试PageControl
的各种交互来切换页面,PageView
展示了SwiftUI
和UIKit
视图如何混合使用。
问题1 下面哪个协议可以用来把UIKit
中的视图控件器桥接进SwiftUI
?
问题2 对于UIViewControllerRepresentable
类型,下面哪个方法可以为它创建一个代理或数据源?
makeUIViewController(context:)
方法中创建UIViewController
实例的地方UIViewControllerRepresentable
类型的初始化器中makeCoordinator()
方法中