SwiftUI八与UIKIT交互

代码下载

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

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

下载起步项目并跟着本篇教程一步步实践,或者查看本篇完成状态时的工程代码去学习,项目文件。

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

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

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

import SwiftUI
import UIKitstruct PageViewController<Page: View>: UIViewControllerRepresentable {var pages: [Page]}

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

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

    func makeUIViewController(context: Context) -> UIPageViewController {let pageViewController = UIPageViewController(transitionStyle: .scroll,navigationOrientation: .horizontal)return pageViewController}

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

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {pageViewController.setViewControllers([UIHostingController(rootView: pages[0])], direction: .forward, animated: true)}

现在,创建了 UIHostingController,它在每次更新时托管页面SwiftUI视图。稍后将通过在页面视图控制器的生命周期中只初始化一次控制器来提高效率。

4、将下载的项目文件 Resources 目录中的图片拖到应用程序的 Asset 目录中。地标的特征图像,如果存在的话,其维度与常规图像不同。

5、将计算属性添加到返回特征图像(如果存在)的 Landmark 结构中。

   var featureImage: Image? {isFeatured ? Image(imageName + "_feature") : nil}

6、添加一个新的 SwiftUI 视图文件,名为 FeatureCard.swift,用于显示地标的特征图像。

import SwiftUIstruct FeatureCard: View {var landmark: Landmarkvar body: some View {landmark.featureImage?.resizable()}
}#Preview {FeatureCard(landmark: ModelData().features[0]).aspectRatio(3 / 2, contentMode: .fit)
}

包括长宽比修改器,这样它就可以模仿视图的长宽比,而 FeatureCard 稍后将预览该视图最终的样子。

7、在图像上叠加有关地标的文本信息。

struct FeatureCard: View {var landmark: Landmarkvar body: some View {landmark.featureImage?.resizable().overlay {TextOverlay(landmark: landmark)}}
}struct TextOverlay: View {var landmark: Landmarkvar gradient: LinearGradient {.linearGradient(Gradient(colors: [.black.opacity(0.6), .black.opacity(0)]),startPoint: .bottom,endPoint: .center)}var body: some View {ZStack(alignment: .bottomLeading) {gradientVStack(alignment: .leading) {Text(landmark.name).font(.title).bold()Text(landmark.park)}.padding()}.foregroundStyle(.white)}
}

接下来,创建另一个SwiftUI视图展示遵循UIViewControllerRepresentable协议的视图。

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

struct PageView<Page: View>: View {var pages: [Page]var body: some View {PageViewController(pages: pages)}
}#Preview {PageView()
}

预览失败是因为Xcode无法推断Page的类型。

9、添加宽高比修改器,更新预览视图,并传入视图数组,预览视图就会开始工作了。

struct PageView<Page: View>: View {var pages: [Page]var body: some View {PageViewController(pages: pages).aspectRatio(3 / 2, contentMode: .fit)}
}#Preview {PageView(pages: ModelData().features.map { FeatureCard(landmark: $0) })
}

创建视图控制器的数据源

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

请添加图片描述

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

1、在 PageViewControlelr 中定义一个嵌套类型 Coordiantor。SwiftUI管理 UIViewControllerRepresentable 类型的 coordinator,并在调用方法时把它作为上下文的一部分。

    class Coordinator: NSObject {var parent: PageViewControllerinit(_ pageViewController: PageViewController) {parent = pageViewController}}

2、在 PageViewController 中添加另一个方法,创建coordinator。SwiftUI在调用 makeUIViewController(context:) 前会先调用 makeCoordinator() 方法,因此在配置视图控制器时是可以访问到coordiantor对象的。

    func makeCoordinator() -> Coordinator {Coordinator(self)}

可以使用coordinator为实现通用的Cocoa模式,例如:代理模式、数据源以及目标-动作。

3、在 Coordinator 中使用 pages 的视图数组初始化控制器数组。

    func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {pageViewController.setViewControllers([context.coordinator.controllers[0]], direction: .forward, animated: true)}class Coordinator: NSObject {var parent: PageViewControllervar controllers = [UIViewController]()init(_ pageViewController: PageViewController) {parent = pageViewControllercontrollers = parent.pages.map { UIHostingController(rootView: $0) }}}

Coordinator 是存储这些控制器的好地方,因为系统只初始化它们一次,并且在需要它们更新视图控制器之前。

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

    class Coordinator: NSObject, UIPageViewControllerDataSource {var parent: PageViewControllervar controllers = [UIViewController]()init(_ pageViewController: PageViewController) {parent = pageViewControllercontrollers = parent.pages.map { UIHostingController(rootView: $0) }}func pageViewController(_ pageViewController: UIPageViewController,viewControllerBefore viewController: UIViewController) -> UIViewController?{guard let index = controllers.firstIndex(of: viewController) else {return nil}if index == 0 {return controllers.last}return controllers[index - 1]}func pageViewController(_ pageViewController: UIPageViewController,viewControllerAfter viewController: UIViewController) -> UIViewController?{guard let index = controllers.firstIndex(of: viewController) else {return nil}if index + 1 == controllers.count {return controllers.first}return controllers[index + 1]}}

5、把coordiantor作为UIPageViewController的数据源。

    func makeUIViewController(context: Context) -> UIPageViewController {let pageViewController = UIPageViewController(transitionStyle: .scroll,navigationOrientation: .horizontal)pageViewController.dataSource = context.coordinatorreturn pageViewController}

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

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

如果要添加一个自定义的 UIPageControl 控件,就需要一种方式能够在 PageView 中跟踪当前展示的页面。

这就需要在 PageView 中声明一个 @State 属性,并传递一个针对该属性的绑定关系给 PageViewController 视图,在 PageViewController 中通过绑定关系更新状态属性,来反映当前展示的页面。

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

    @Binding var currentPage: Intfunc updateUIViewController(_ pageViewController: UIPageViewController, context: Context) {pageViewController.setViewControllers([context.coordinator.controllers[currentPage]], direction: .forward, animated: true)}

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

struct PageView<Page: View>: View {var pages: [Page]@State private var currentPage = 0var body: some View {PageViewController(pages: pages, currentPage: $currentPage).aspectRatio(3 / 2, contentMode: .fit)}
}

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

    @State private var currentPage = 1

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

struct PageView<Page: View>: View {var pages: [Page]@State private var currentPage = 0var body: some View {VStack {PageViewController(pages: pages, currentPage: $currentPage)Text("Current Page: \(currentPage)")}.aspectRatio(3 / 2, contentMode: .fit)}
}

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

class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate {func pageViewController(_ pageViewController: UIPageViewController,didFinishAnimating finished: Bool,previousViewControllers: [UIViewController],transitionCompleted completed: Bool) {if completed,let visibleViewController = pageViewController.viewControllers?.first,let index = controllers.firstIndex(of: visibleViewController) {parent.currentPage = index}}...

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

    func makeUIViewController(context: Context) -> UIPageViewController {let pageViewController = UIPageViewController(transitionStyle: .scroll,navigationOrientation: .horizontal)pageViewController.dataSource = context.coordinatorpageViewController.delegate = context.coordinatorreturn pageViewController}

添加一个自定义PageControl

准备为包裹在 UIViewRepresentable 视图中的子视图上添加了一个自定义 UIPageControl。

请添加图片描述

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

struct PageControl: UIViewRepresentable {var numberOfPages: Int@Binding var currentPage: Intfunc makeUIView(context: Context) -> UIPageControl {let control = UIPageControl()control.numberOfPages = numberOfPagesreturn control}func updateUIView(_ uiView: UIPageControl, context: Context) {uiView.currentPage = currentPage}
}

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

    var body: some View {ZStack(alignment: .bottomTrailing) {PageViewController(pages: pages, currentPage: $currentPage)PageControl(numberOfPages: pages.count, currentPage: $currentPage).frame(width: CGFloat(pages.count * 18)).padding(.trailing)}.aspectRatio(3 / 2, contentMode: .fit)}

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

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

    func makeCoordinator() -> Coordinator {Coordinator(self)}class Coordinator: NSObject {var control: PageControlinit(_ control: PageControl) {self.control = control}@objcfunc updateCurrentPage(sender: UIPageControl) {control.currentPage = sender.currentPage}}

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

    func makeUIView(context: Context) -> UIPageControl {let control = UIPageControl()control.numberOfPages = numberOfPagescontrol.addTarget(context.coordinator,action: #selector(Coordinator.updateCurrentPage(sender:)),for: .valueChanged)return control}

5、最后,在 CategoryHome 中,用新的页面视图替换占位符特征图像。

struct CategoryHome: View {@Environment(ModelData.self) var modelData@State private var showingProfile = falsevar body: some View {NavigationSplitView {List {PageView(pages: modelData.features.map { FeatureCard(landmark: $0) }).listRowInsets(EdgeInsets())ForEach(modelData.categories.keys.sorted(), id: \.self) { key inCategoryRow(categoryName: key, items: modelData.categories[key]!)}.listRowInsets(EdgeInsets())}.listStyle(.inset).navigationTitle("Featured").toolbar {Button {showingProfile.toggle()} label: {Label("User Profile", systemImage: "person.crop.circle")}}.sheet(isPresented: $showingProfile) {ProfileHost().environment(modelData)}} detail: {Text("Select a Landmark")}}
}

6、现在就可以尝试 PageControl 的各种交互来切换页面,PageView展示了SwiftUI和UIKit视图如何混合使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/37046.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Meta CEO 扎克伯格批评闭源AI竞争对手:称其试图“创造上帝”|TodayAI

美国社交媒体巨头Meta(Facebook母公司)的CEO马克扎克伯格&#xff08;Mark Zuckerberg&#xff09;近日在一次采访中&#xff0c;公开批评了那些他认为不够开放的AI竞争对手&#xff0c;称他们的行为就像是在“创造上帝”。扎克伯格坚定表示&#xff0c;AI技术不应该被某一家公…

SysML与MBSE的关系

SysML与MBSE的关系 对于任何基于模型的系统工程 &#xff08;MBSE&#xff09; 方法&#xff0c;推荐的最佳实践是基于模型的语言、基于模型的工具、基于模型的流程和基于模型的架构框架的协同应用&#xff0c;如下图所示 系统架构四元组 图。经过十年将SysML应用于棘手的系统…

戴尔md3400存储控制器脱机故障 电池故障处理

看了一下网上关于DELL MD系列存储故障处理的文档还是比较少的&#xff0c;最近处理了一些关于MD系列存储的问题&#xff0c;稍微整理整理就分享一下&#xff0c;各位喜欢摸索的朋友可以稍稍做些参考&#xff0c;当然如果想寻求外援的也可以快速的找到合适的人。以便安全又快捷的…

C语言基础——操作符

ʕ • ᴥ • ʔ づ♡ど &#x1f389; 欢迎点赞支持&#x1f389; 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;C语言基础&#xff1b; 文章目录 前言 一、操作符的分类 二、二进制和进制转换 2.1 二进制转十进制 2.1.1 十…

零知识证明基础:数字签名

1、绪论 数字签名(Digital Signature)&#xff0c;也称电子签名&#xff0c;是指附加在某一电子文档中的一组特定的符号或代码。它利用密码技术对该电子文档进行关信息提取并进行认证形成&#xff0c;用于标识签发者的身份以及签发者对电子文档的认可&#xff0c;并能被接收者…

pyqt5 制作视频剪辑软件,切割视频

该软件用于切割视频,手动选取视频片段的起始帧和结束帧并保存为json文件。gui界面如下:包含快进、快退、暂停等功能, 代码如下: # coding=UTF-8 """ theme: pyqt5实现动作起始帧和结束帧的定位,将定位到的帧数保存json文件 time: 2024-6-27 author: cong…

详细介绍LP-SCADA系统的核心数据采集单元

关键字:LP-SCADA系统, 传感器可视化, 设备可视化, 独立SPC系统, 智能仪表系统,SPC可视化,独立SPC系统 SCADA系统的数据采集功能是其核心组成部分&#xff0c;它允许系统从各种传感器、仪器和设备中收集实时数据。以下是SCADA系统数据采集功能的详细描述&#xff1a; 传感器和…

Kotlin vs Java:深入解析两者之间的最新差异与优劣(全面指南)

文章目录 1. 概述2. 语法简洁性3. 空安全4. 扩展函数5. 协程6. 数据类7. 智能类型转换8. 默认参数与命名参数9. 无 checked exceptions10. 单例模式总结 &#x1f389;欢迎来到Java学习路线专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨…

Java对象类辨识指南:Object与Objects类的区别详解

今天在写lambda表达式时&#xff0c;用filter来做过滤判断我的结果是否为null时使用到了Objects.nonNull&#xff0c;但是敲着敲着发现不对劲&#xff0c;怎么没有nonNull方法?? 其实时我少敲了一个s&#xff0c;当时自己并没有很清楚Object和Objects两者之前的区别&#xf…

LangGPT:高质量提示词框架

题目&#xff1a;LangGPT: Rethinking Structured Reusable Prompt Design Framework for LLMs from the Programming Language作者: Ming Wang; Yuanzhong Liu; Xiaoming Zhang; Songlian Li; Yijie Huang; Chi Zhang; Daling Wang; Shi Feng; Jigang LiDOI: 10.48550/arXiv.2…

Qt自定义信号

1.Teacher类下定义信号signals: Student类下定义槽函数&#xff1a; Teacher.h #pragma once#include <QObject>class Teacher : public QObject {Q_OBJECTpublic:Teacher(QObject *parent);~Teacher(); signals:void Ask(); //老师向学生提问void Ask(QString str);…

量化投资 日周月报 2024-06-28

文章 深度学习在量化交易中的应用:在BigQuant量化交易平台的文章中,探讨了深度学习在量化交易中,特别是在因子挖掘方面的应用。文章提到,随着传统线性模型的潜力逐渐枯竭,非线性模型逐渐成为量化交易的主要探索方向。深度学习因其对非线性关系的拟合能力,在量化交易中展现…

qmt量化交易策略小白学习笔记第52期【qmt编程之商品期货数据】

qmt编程之获取商品期货数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 主力合约生成规则 每个品种只有一个主连合约。主连合约于下一个交易日进行指向切换&#xff0c;切换前主连合约不变…

Unity Animator 运行时修改某个动画状态的播放速度

1.添加动画参数&#xff0c;选择需要动态修改速度的动画状态 2.在属性面板种设置速度倍速参数

HarmonyOS ArkUi ArkWeb加载不出网页问题踩坑

使用 使用还是比较简单的&#xff0c;直接贴代码了 别忘了配置网络权限 Entry Component struct WebPage {State isAttachController: boolean falseState url: string State title: string Prop controller: web_webview.WebviewController new web_webview.WebviewCont…

自定义注解+AOP形式监控接口调用日志

目的&#xff1a; 通过自定义注解&#xff0c;在需要监控接口调用输出日志的类或方法上&#xff0c;加上自定义注解&#xff0c;实现无侵入式接口监控。 实现&#xff1a; idea结构 1、导入pom <dependency><groupId>org.aspectj</groupId><artifactI…

ASUS/华硕天选Air 2021 FX516P系列 原厂win10系统

安装后恢复到您开箱的体验界面&#xff0c;带原机所有驱动和软件&#xff0c;包括myasus mcafee office 奥创等。 最适合您电脑的系统&#xff0c;经厂家手调试最佳状态&#xff0c;性能与功耗直接拉满&#xff0c;体验最原汁原味的系统。 原厂系统下载网址&#xff1a;http:…

FTP 文件传输协议:概念、工作原理;上传下载操作步骤

目录 FTP 概念 工作原理 匿名用户 授权用户 FTP软件包 匿名用户上传下载实验步骤 环境配置 下载 上传 wget 授权用户上传下载步骤 root用户登录FTP步骤 监听 设置端口号范围 修改用户家目录 匿名用户 授权用户 FTP 概念 FTP&#xff08;File Transfer Prot…

JAVA设计模式-大集合数据拆分

背景 我们在做软件开发时&#xff0c;经常会遇到把大集合的数据&#xff0c;拆分成子集合处理。例如批量数据插入数据库时&#xff0c;一次大约插入5000条数据比较合理&#xff0c;但是有时候待插入的数据远远大于5000条。这时候就需要进行数据拆分。数据拆分基本逻辑并不复杂&…

毅速丨金属3D打印是制造业转型升级的重要技术

随着科技的进步&#xff0c;金属3D打印技术已成为制造业升级的重要驱动力。它以其独特的优势&#xff0c;正引领着制造业迈向新的未来。 金属3D打印技术的突破&#xff1a; 设计自由。金属3D打印能制造任意形状和结构的零件&#xff0c;为设计师提供了无限的创意空间。 快速制…