SwiftUI中的AnyLayout的理解与使用以及自定义Layout

AnyLayoutSwiftUI中的一个类型擦除容器,它可以包装任何遵循Layout协议的布局。这意味着我们可以使用AnyLayout来抽象具体的布局类型,从而在运行时决定使用哪种布局。这种灵活性极大地增强了UI组件的可重用性和适应性。

AnyLayout可以在保持视图identifier的同时在布局(Layout)之间进行转换。

iOS 16以前的处理

在iOS 16以前,如果如果想要改变布局,或者在横竖屏切换的时候改变布局,我们一般都是通过if判断来处理加载不同布局的视图,比如下面:

struct AnylayoutDemo: View {@Environment(\.horizontalSizeClass) var horizontalSizeClassvar body: some View {VStack {if horizontalSizeClass == .compact {VStack {Text("One")Text("Two")Text("Three")}} else {HStack {Text("One")Text("Two")Text("Three")}}}.font(.largeTitle)}
}

横竖屏切换的时候horizontalSizeClass会变化,进而加载不同布局的视图。在iPhone上不是所有设备的horizontalSizeClass在横竖屏切换的时候都会变化,本文只是示例。

上面代码中horizontalSizeClass变化的时候,SwiftUI渲染if不同分支的代码,这相当于移除一部分组件,又添加了一部分新组件上去。

AnyLayout

引入AnyLayout是为了平滑布局之间的过渡。它是Layout协议的类型擦除实例。使用AnyLayout实例允许我们在不破坏底层子视图状态的情况下改变布局类型。

因为视图层次结构的标识(identifier)始终保持不变,SwiftUI将其视为一个改变的视图,而不是像if-else那样的新视图。

现在将上面的代码采用AnyLayout,如下:

struct AnylayoutDemo: View {@Environment(\.horizontalSizeClass) var horizontalSizeClassvar body: some View {let layout = horizontalSizeClass == .compact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())VStack {layout {Text("One")Text("Two")Text("Three")}}.font(.largeTitle)}
}

上面代码中通过horizontalSizeClass不同的值创建了AnyLayout的实例layout,而该layout代替了HStackVStack

AnyLayout中使用的类型必须符合Layout协议。SwiftUI提供了四个新版本的布局HStackLayout, VStackLayout, ZStackLayoutGridLayout

自定义AnyLayout

SwiftUI中,实现自定义布局涉及到遵循Layout协议。这个协议要求实现下面两个方法。
下面,我将通过一个简单的例子来展示如何创建一个自定义布局,这个布局将会简单地将子视图水平排列,并且在它们之间添加固定的间距。

// 用于计算布局所需的总体尺寸。这个方法需要返回一个 CGSize,表示布局的理想大小。
func sizeThatFits(proposal: ProposedViewSize,subviews: Subviews,cache: inout ()
) -> CGSize
// 用于放置子视图。你需要指定每个子视图的位置。
func placeSubviews(in bounds: CGRect,proposal: ProposedViewSize,subviews: Subviews,cache: inout ()
)

首先,定义一个结构体,让它遵循Layout协议。

struct SimpleHorizontalLayout: Layout {let spacing: CGFloat // 用于定义子视图之间的间距init(spacing: CGFloat) {self.spacing = spacing}
}

这里,SimpleHorizontalLayout结构体有一个spacing属性,用于控制子视图之间的间距。

实现sizeThatFits方法:

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {var totalWidth: CGFloat = 0var maxHeight: CGFloat = 0for subview in subviews {let subviewSize = subview.sizeThatFits(proposal)totalWidth += subviewSize.widthmaxHeight = max(maxHeight, subviewSize.height)}totalWidth += CGFloat(subviews.count - 1) * spacing // 添加间距return CGSize(width: totalWidth, height: maxHeight)
}

实现placeSubviews方法:

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {var currentX: CGFloat = bounds.minXfor subview in subviews {let subviewSize = subview.sizeThatFits(proposal)subview.place(at: CGPoint(x: currentX, y: bounds.midY - subviewSize.height / 2), anchor: .topLeading, proposal: ProposedViewSize(width: subviewSize.width, height: subviewSize.height))currentX += subviewSize.width + spacing}
}

最终,在调用的地方:

var body: some View {let layout = AnyLayout(SimpleHorizontalLayout(spacing: 10))layout {Text("One")Text("Two")Text("Three")}.font(.largeTitle)
}

自定义Layout完整代码如下:

struct SimpleHorizontalLayout: Layout {let spacing: CGFloat // 用于定义子视图之间的间距init(spacing: CGFloat) {self.spacing = spacing}func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {var totalWidth: CGFloat = 0var maxHeight: CGFloat = 0for subview in subviews {let subviewSize = subview.sizeThatFits(proposal)totalWidth += subviewSize.widthmaxHeight = max(maxHeight, subviewSize.height)}totalWidth += CGFloat(subviews.count - 1) * spacing // 添加间距return CGSize(width: totalWidth, height: maxHeight)}func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {var currentX: CGFloat = bounds.minXfor subview in subviews {let subviewSize = subview.sizeThatFits(proposal)subview.place(at: CGPoint(x: currentX, y: bounds.midY - subviewSize.height / 2), anchor: .topLeading, proposal: ProposedViewSize(width: subviewSize.width, height: subviewSize.height))currentX += subviewSize.width + spacing}}
}

写在最后

本文主要探索了一下iOS 16以前和以后针对不同布局的一些写法,在iOS 16及以后,我们可以使用AnyLayout做界面的动态布局,整体上都好于iOS 16以前的写法,此外,我们也可以自定义Layout,实现我们想要的布局。

最后,希望能够帮助到有需要的朋友,如果您觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

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

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

相关文章

Android 绑定服务的5个问题。

1.android studio 目录结构改变了。为什么会报R 资源文件找不到。 在写项目的时候经常需要改到。 gradle文件里的域名名字要改变下。 2.Caused by: android.app.BackgroundServiceStartNotAllowedException: Not allowed to start service Intent { cmpcom.zjtzsw.sbkDevice/…

oracle 数字或者小数格式化

select trim(. from to_char(1,fm9999990.9999)),trim(. from to_char(0.1,fm9999990.9999)) from dual

视觉SLAM十四讲:从理论到实践(Chapter8:视觉里程计2)

前言 学习笔记,仅供学习,不做商用,如有侵权,联系我删除即可 一、目标 1.理解光流法跟踪特征点的原理。 2.理解直接法是如何估计相机位姿的。 3.实现多层直接法的计算。 特征点法存在缺陷: 二、光流(Optical Flow) …

【Qt知识】Qt中的对象树是什么?

在深入Qt编程的世界时,你会频繁遇到一个核心概念——对象树(Object Tree)。这个概念是Qt框架管理内存、处理事件和组织用户界面元素的基础。 什么是Qt对象树? 如果你的Qt应用程序就像一片茂盛的森林,而这片森林中的每…

multisim仿真电路图

在Multisim中进行电路仿真时,创建和配置电路图通常涉及一系列步骤。以下是创建和配置 Multisim仿真电路图的详细步骤,以确保格式清晰并充分参考了相关文章中的信息: 1. 创建电路图 打开Multisim软件。 选择“新建电路图文件”或使用快捷键Ctrl+N。 在新建的电路文件中,…

C++学习笔记(22)——多态

目录 [TOC](目录) 比喻与理解1. 多态的概念2. 多态的定义及实现2.1多态的构成条件2.2 虚函数2.3虚函数的重写2.3.1 虚函数重写的两个例外:1. 协变(基类与派生类虚函数返回值类型不同)2. 析构函数的重写(基类与派生类析构函数的名字不同) 2.4 C11 override 和 final2…

如何保证每次画出的都同一张人脸?AI绘画Stable Diffusion的Reference only教程

Ai绘画有一个很现实的问题,要保证每次画出的都是同一个人物的话,很费劲。 Midjourney就不必说了,人物的高度一致性一直得不到很好的解决。而在Stable Diffusion(SD)中,常用办法是通过同一个Seed值&#xf…

【Python Cookbook】S01E16 同时对数据做转换和换算

目录 问题解决方案讨论 问题 当换算(sum(), min(), max())同时遇到转换或筛选,怎么做? 解决方案 有一种非常优雅的方法,可以将数据换算和转换结合在一起——在函数参数中使用 生成器表达式。 一个直观的案例&#…

ATA-7030高压放大器应用分享:铁电体和压电材料

经过多年研究探索,Aigtek安泰电子功率放大器目前已拥有一众完善的系列功放产品,可为超声测试、生物医疗、水声测试、压电驱动、电磁驱动、MEMS测试等众多研究项目提供系统性的行业测试方案,其中对于压电极化测试、铁电极化测试,Ai…

GPT-4o:人工智能新纪元的突破与展望

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

介绍 Vue 的常用模板语法,如插值、指令、事件处理、条件渲染、列表渲染

Vue 的模板语法包含以下常用的语法元素: 1:插值: 使用 {{ }} 语法在模板中插入动态数据,如 {{ message }}。 也可以使用 v-text 指令来设置元素的文本内容。 2:指令: 指令是带有 v- 前缀的特殊属性,用于在模板中添加动态行为。 常见指令包括: v-if/v-e…

香橙派AI Pro开箱初体验

一、前言 上周很荣幸在CSDN上收到香橙派的测评邀请,这是一款专为边缘计算和嵌入式AI应用设计的高性能计算平台。因为之前一直做的是GPU Tensorrt部署相关工作,对边缘计算平台也不是很熟悉,花了一些时间摸索,今天我就简单与大家分…

npm发布自己的组件组件/插件包

前言 在项目开发过程中,我们经常会用到一些工具函数。虽然像lodash这样的库提供了丰富的功能,但其体积较大,有时我们更倾向于使用自己封装的轻量函数库。这篇博客将以创建并发布一个工具函数库为例,详细介绍如何在npm上发布自己的…

动态二维码与静态二维码,有何区别,如何使用?

近年来,二维码势不可挡席卷而来,并在我们的生活中占据了一席之地,付款、加好友、点餐、活动签到、电子门票、商品信息查询、分享内容等等,哪都有它。 二维码诞生于日本,就是用特定几何图形,按规律在平面上分…

使用python绘制华夫饼图

使用python绘制华夫饼图 华夫饼图效果代码 华夫饼图 华夫饼图(Waffle Chart)是一种数据可视化图表,用于显示数据在一个网格中的分布情况。它类似于饼图,通过将数据划分为等大小的方块来表示不同类别的比例。华夫饼图的优势在于它…

Python编程学习第一篇——Python零基础快速入门(五)—集合

前面几节介绍了元组、列表,今天我们来学习集合。 Python中的集合(set)是一种无序的、可变的、没有重复元素的数据类型。集合是由一对花括号{}括起来的元素组成,每个元素之间用逗号分隔。 以下是集合的一些常见操作: …

解决 Mac Django 连接Mysql 出现 image not found 问题

最近在使用 Django 框架,因为升级到4.2版本了,对应的本机 Mysql 5.7 就不适用了,于是升级到了 Mysql 8.0,写好代码之后出现如下错误: 仔细分析一下错误的描述: ImportError: dlopen(/Library/Frameworks/P…

1122 找奇葩

solution 目标数&#xff1a;出现次数为奇数次&&该数本身为奇数 #include<iostream> using namespace std; const int maxn 1e5 10; int flag[maxn] {0}; int main(){int n, x;scanf("%d", &n);for(int i 1; i < n; i){scanf("%d&qu…

GPT-4o:智能新纪元的破晓

在人工智能领域&#xff0c;每一次技术的飞跃都伴随着无数目光的聚焦和期待。最近&#xff0c;GPT-4o的问世无疑成为了科技界的一颗耀眼的新星。作为OpenAI继GPT-3之后的又一力作&#xff0c;GPT-4o不仅承载了前代模型的优良基因&#xff0c;更在多个维度上实现了质的飞跃。 首…

EIS 2019 webshell

请求中可以确定是http POST流量 同时可以判断是 蚁剑的流量 进一步过滤 http.request.method "POST" 直接追踪其tcp流 得到 列举部分 eVAl(cHr(0x40).ChR(0x69).ChR(0x6e).ChR(0x69).ChR(0x5f).ChR(0x73).ChR(0x65).ChR(0x74).ChR(0x28)直接输出一下 内容 <…