SwiftUI - 界面布局知识点

前言

SwiftUI采用的布局方式是和Flutter一样是弹性布局,而不是iOS之前的坐标轴的方式布局,不用准确的设置出位置大小,只需要设置当前视图大小及视图间排布的方式。灵活性增强,布局操作简便,SwiftUI与Flutter布局原理一样,学完一个再学另一个都很方便。

1、VStack 、HStack 、ZStack、LazyVStack、LazyHStack

SwiftUI里的三大容器视图,在布局中是基本的容器类视图,子视图(如Text、Image、Button等)放在它们里面按一定的规则排序。子视图之间默认的spacing=8(即不设置时候间距为8),子视图默认的padding=16(需要设置.padding()才会有),如Text,默认的.padding()=.padding(16)。容器视图的区域为所有子视图所占的矩形空间,如果只有一个子视图,那大小就是子视图的大小。
VStack:纵向布局容器,容器内子视图呈纵向排列,从上往下排列。
HStack:横向布局容器,容器内子视图呈横向排列,从左往右排列。
ZStack:深度布局容器,容器内子视图呈前后排列,从里到外排列(屏幕为参照),默认的优先级zIndex=0

LazyVStack:纵向布局容器,容器内子视图呈纵向排列,从上往下排列,LazyVStack特点是仅在需要时创建,如果容器子视图太多,超时屏幕太多,可以使用它节省内存。
LazyHStack:横向布局容器,容器内子视图呈横向排列,从左往右排列,LazyHStack特点是仅在需要时创建,如果容器子视图太多,超时屏幕太多,可以使用它节省内存,只加载屏幕上需要展示的View,当滑动时才去展示更多的View,即触发了懒加载机制,当我们去掉ScrollView后,发现无法触发懒加载。当我们把ForEach替换成用Group包装的多个组后,也不能实现懒加载效果。所以LazyStack想要触发懒加载机制,ScrollView及ForEach缺一不可。

2、Spacer

Spacer():一个看似透明的视图,在布局中起重要作用,它起一个撑满的作用,比如Hstack中的一个Text想在屏幕左边,那么右边添加一个Spacer即可,Spacer就会将右边剩余部分撑满,Text就会被撑到左边。在Vstack中同样可以控制一个视图在纵向的位置。如果给它设置宽度或者高度,那效果也会不一样。

HStack{
          Text("测试")
          Spacer()
       }
       .padding()
       .background(Color.green)

注意:HStack的背景颜色设置是.background,而不是.backgroundColor,background是在底部新建一个View。而且它与padding的顺序上也是有讲究的,谁在前谁在后效果都是不一样的。不妨可以试试看。

先调用padding,再调用background,效果如下:

先调用background,再调用padding,效果如下:

HStack{
          Text("测试")
          Spacer()
      }
      .background(Color.green)
      .padding()

3、Devider()

SwiftUI中的表示分割线的一条线,在容器内以交叉轴方向做延伸,在不设置长度的情况下会撑满容器的最大可显示区域交叉轴。这样容器类的区域也会随着Devider去放大。当然也可以给它设置相应的宽或高来满足我们的需求。

4、Group与GroupBox
字面意思看是一个“分组”和“分组盒子”,可以将一组里的所有视图设置统一的样式,示例如下:

Group{
            HStack{
                Text("测试1")
                Spacer()
            }
            HStack{
                Text("测试2")
                Spacer()
            }
        }.padding()
         .background(Color.green)

对比代码:

VStack{
                   Group{
                       Text("测试组一")
                   }
            if #available(iOS 14.0, *) {
                GroupBox{
                    Text("测试组二")
                }.padding().background(Color.red)
                GroupBox{
                    Text("测试组三")
                }.padding().colorMultiply(.red)
            } else {
                // Fallback on earlier versions
            }
                   ForEach(0...3,id:\.self){
                       index in
                       if #available(iOS 14.0, *) {
                           GroupBox(label: Text("第\(index+1)组"), content: {
                               Text("Content").frame(width: 120, height: 20, alignment: .center).background(Color.green)
                           })
                       } else {
                           // Fallback on earlier versions
                       }
                   }
            }

对比效果:

可见GroupBox就是一个分组的盒子,而且可以嵌套使用,图中外Box显示全黄色以及内Box显示全红色的效果使用的是colorMultiply而不是background,因为background只是在底部添加一个View,colorMultiply则是在最顶部也就是屏幕最外面添加一个遮罩层,就像在做颜色混合计算一样,覆盖上去,会影响子视图显示的颜色(如果子视图设置了别的颜色,此处未设置,所以随Box.colorMultiply颜色)。

OutlineGroup:类似文件夹的分层效果,可实现树状结构的分层效果,可折叠,可展开。

DisClosureGroup: 可折叠的分组,类似于List里的.listStyle(.sidebar)样式,是GroupBox中的子视图可折叠可展开样式。嵌套使用时候即可实现OutlineGroup分层结构效果,树状结构效果上个人感觉比OutlineGroup效果更好。

ControlGroup:类似于UIKit中的Segmented的样式。如果想改变样式,可以更改.controlgRgoupStyle()

5、overlay

在实现前后顺序的功能,布局上除了ZStack,我们还可以使用overlay
如系统计算器里按钮上的文字就可以使用overlay来实现。

Button(action: {
        }){
            Text("")
        }.frame(width: 50, height: 50).background(Color.green).cornerRadius(25)
            .overlay(){
                Image(systemName: "person")
            }

就很简单的实现了按钮上添加图片的功能。默认图片展示在按钮中心点上。
利用ZStack实现相同功能如下:

ZStack{
            Button(action: {
                
            }){
                Text("")
            }.frame(width: 50, height: 50).background(.red).cornerRadius(25)
            Image(systemName: "person")
        }

而且区别就是.overlay是按钮的一个Modifier,而ZStack是一个容器。具体的还是要根据项目实际功能来选择哪种方式。

6、绝对位置、相对位置

position(x:,y:):绝对位置,设置视图的中心点在距离左上角(x,y)的位置。

//第一段
        Text("测试")
                    .font(.title)
                    .background(.red)
                    .padding()
                    .position(x:100,y:100)
        //第二段
        Text("测试")
                    .padding()
                    .font(.title)
                    .background(.red)
                    .position(x:100,y:100)
        //第三段
        Text("测试")
                    .padding()
                    .font(.title)
                    .position(x:100,y:100)
                    .background(.red)
        //第四段
        Text("测试")
                    .padding()
                    .font(.title)
                    .background(.red)
                    .position(x:100,y:100)
                    .background(.green)

效果如下:

可以看出增加position后显示区域感觉变大了,也就是安全区域,原因是position会新建一个View作为Text的父视图,所以position之后的颜色设置的是position所返回的View的背景颜色。

当我们使用position()时,我们得到一个占据所有可用空间的新视图,因此它可以将其子项(文本)定位在正确的位置。 

offset():相对位置,相对position来说不会新建一个父视图,而是直接将中心点按offset所标大小移动。只是去改变显示的位置。

当我们使用offset()修饰符时,我们是在更改应呈现视图的位置,而不会实际更改其底层几何图形 。
注意:从这里我们可以看出函数响应式代码的细节,要注意顺序的前后对结果的影响。

7、GeometryReader 

使用它的大小和坐标来确定子视图的布局,

在使用 GeometryReader时,你应该始终牢记 SwiftUI 的三步布局系统:父级为子级建议尺寸,子级使用它来确定自己的尺寸,父级使用它来适当地定位子级。

在其最基本的用法中,GeometryReader的作用是让我们读取父级建议的尺寸,然后使用它来操纵我们的视图。例如,我们可以用GeometryReader使文本视图拥有所有可用宽度的 90%,而不管其内容如何:

struct ContentView: View {
    var body: some View {
        GeometryReader { geo in
            Text("Hello, World!")
                .frame(width: geo.size.width * 0.9)
                .background(.red)
        }
    }
} 

geo传入的参数是GeometryProxy,它包含建议的大小、已应用的任何安全区域插图,以及我们稍后将查看的读取帧值的方法。

GeometryReader有一个有趣的副作用,一开始可能会让你大吃一惊:返回的视图具有灵活的首选大小,这意味着它将根据需要扩展以占用更多空间。如果将GeometryReader放入一个VStack然后在其下方放置更多文本,你可以看到它的实际效果,如下所示: 

struct ContentView: View {
    var body: some View {
        VStack {
            GeometryReader { geo in
                Text("Hello, World!")
                    .frame(width: geo.size.width * 0.9, height: 40)
                    .background(.red)
            }

            Text("More text")
                .background(.blue)
        }
    }
} 

你会看到“更多文本”被推到屏幕底部,因为GeometryReader占用了所有剩余空间。要查看它的实际效果,请将background(.green)其添加为GeometryReader的修饰符,然后你就会看到它有多大。注意:这是首选大小,而不是绝对大小,这意味着它仍然可以根据其父级灵活调整。

当谈到读取视图的框架时,GeometryProxy提供了一种frame(in:)方法而不是简单的属性。这是因为“框架”的概念包括 X 和 Y 坐标,它们孤立起来没有任何意义——你想要视图的绝对 X 和 Y 坐标,还是它们的 X 和 Y 坐标与其父坐标的比较?

SwiftUI 将这些选项称为coordinate spaces(坐标空间),特别是这两个称为全局空间(测量我们的视图相对于整个屏幕的框架)和局部空间(测量我们的视图相对于其父级的框架)。我们还可以通过将coordinateSpace()修饰符附加到视图来创建自定义坐标空间——然后它的任何子元素都可以读取相对于该坐标空间的框架。

为了演示坐标空间是如何工作的,我们可以在各种堆栈中创建一些示例视图,将自定义坐标空间附加到最外面的视图,然后将一个添加到其中的一个onTapGesture视图,以便它可以全局、局部地打印出框架,并使用自定义坐标空间。 

struct OuterView: View {
    var body: some View {
        VStack {
            Text("Top")
            InnerView()
                .background(.green)
            Text("Bottom")
        }
    }
}

struct InnerView: View {
    var body: some View {
        HStack {
            Text("Left")
            GeometryReader { geo in
                Text("Center")
                    .background(.blue)
                    .onTapGesture {
                        print("Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
                        print("Custom center: \(geo.frame(in: .named("Custom")).midX) x \(geo.frame(in: .named("Custom")).midY)")
                        print("Local center: \(geo.frame(in: .local).midX) x \(geo.frame(in: .local).midY)")
                    }
            }
            .background(.orange)
            Text("Right")
        }
    }
}

struct ContentView: View {
    var body: some View {
        OuterView()
            .background(.red)
            .coordinateSpace(name: "Custom")
    }
} 

该代码运行时获得的输出取决于你使用的设备,但这是我得到的:

全局中心:189.83 x 430.60
定制中心:189.83 x 383.60
局部中心:152.17 x 350.96
这些尺寸大多不同,因此希望你能全面了解这些框架的工作原理:

全局中心 X 为 189 意味着几何阅读器的中心距屏幕左边缘 189 点。
全局中心 Y 为 430 表示文本视图的中心距屏幕顶部边缘 430 点。这并没有死在屏幕中央,因为顶部比底部有更多的安全区域。
自定义中心 X 为 189 意味着文本视图的中心距离拥有“自定义”坐标空间的任何视图的左边缘 189 点,在我们的例子中,这是因为我们将OuterView附加到ContentView. 该数字与全局位置匹配,因为OuterView水平地从边到边延伸。
自定义中心 Y 为 383 表示文本视图的中心距 OuterView的上边缘 383 点。该值小于全局中心 Y,因为OuterView没有延伸到安全区域。
局部中心 X 为 152 意味着文本视图的中心距离其直接容器的左边缘 152 点,在本例中为GeometryReader.
350 的局部中心 Y 意味着文本视图的中心距离其直接容器的顶部边缘 350 点,这也是GeometryReader.
你要使用哪个坐标空间取决于你要回答的问题:

想知道这个视图在屏幕上的什么位置?使用全局空间.global
想知道此视图相对于其父视图的位置吗?使用本地空间.local
知道这个视图相对于其他视图的位置是什么?使用自定义空间.named()。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flowable串行流程移动活动

在Activiti6和Activiti7中跳转节点都要自己实现,Flowable增加了这个功能。 一:简介 串行流程是指简单的一条线的流程,流程中如果包含会签、排它网关也算串行流程。 节点移动有三种方式: 向前移动foreward。向后移动back。直接跳…

一文看懂香港优才计划和高才通计划的区别和优势?如何选?

一文看懂香港优才计划和高才通计划的区别和优势?如何选? 为什么很多人都渴望有个香港身份? 英文这里和内地文化相近,语言相通,同时税率较低、没有外汇管制,有稳定金融体制和良好的营商环境,诸多…

java入门, 记录检测网络

一、需求 在开发中,我们经常需要本地连接服务器,或者数据库这些机器或者组件,但是有时候网络不通,我们怎样检测,除了ping 和 telnet 还需要那些常用的技能。 二、检测网络 1、一般我们先ping一些需要连接的网络ip 或…

WY-35A4三相欠压继电器 导轨安装,延时动作0-99.99s可调

系列型号 单相 JY-45A1电压继电器;JY-45B1电压继电器; JY-45C1电压继电器;JY-45D1电压继电器; JY-41A1电压继电器;JY-41B1电压继电器; JY-41C1电压继电器;JY-41D1电压继电器; …

企业数字化建设之——老板关注的IT指标有哪些 ?

投资回报ROI | 商业价值 | 系统可用性 | 业务的参与程度 | 技术债务指数 降本,增效是IT部门工作的永恒话题 ,降低成本 ,增加效益 ,降本增效的工作方向: 1 年初KPI目标、目标完成情况、关键证据、公司主线工作…

主流接口测试框架对比

📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…

基于SSM的供电所档案管理系统

末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…

前端入门(二)js速成与vue脚手架搭建

文章目录 JS常用API操作对象操作对象增删改查js深拷贝、浅拷贝js实现深拷贝的方式 安全访问 JS常用API操作 对象操作 对象增删改查 创建对象 let obj {}新增属性 obj.a 1 、obj[a] 1修改属性 obj.a ‘a’查询属性 obj.a 、obj[a]删除属性 delete obj.a js深拷贝、浅拷贝…

【无线网络技术】——无线传输技术基础(学习笔记)

目录 🕒 1. 无线传输媒体🕘 1.1 地面微波🕘 1.2 卫星微波🕘 1.3 广播无线电波🕘 1.4 红外线🕘 1.5 光波 🕒 2. 天线🕘 2.1 辐射模式🕘 2.2 天线类型🕤 2.2.1 …

Power Automate-与Microsoft Forms连接

创建自动化云端流,流的触发器选择第一个提交新回复时 点击蓝色的Change connection,登录创建Microsoft Forms表单的账号 选择提前创建的表单;如果想连接其他账号创建的Microsoft Forms表单,可以再次点击蓝色的Change connection&a…

RT-DETR算法优化改进:Backbone改进|RIFormer:无需TokenMixer也能达成SOTA性能的极简ViT架构 | CVPR2023

💡💡💡本文独家改进:RIFormer助力RT-DETR ,替换backbone, RIFormer-M36的吞吐量可达1185,同时精度高达82.6%;而PoolFormer-M36的吞吐量为109,精度为82.1%。 推荐指数:五星 RT-DETR魔术师专栏介绍: https://blog.csdn.net/m0_63774211/category_12497375.html …

SPL机制与使用,组件化技术核心点打法

什么是SPI SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。 SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加…

springboot项目的可执行jar以后台本地服务的方式运行在Windows机器上

文章目录 先上一个效果图准备可执行文件注册及启动服务用到的工具 前段时间遇到一个项目,需要我们提供一个驱动控件,可以以后台服务的方式运行在Windows机器上。开始寻找各种解决办法。 最后发现一个不错的解决方式。分享给大家一下。 先上一个效果图 准…

vue2项目从0搭建(一):项目搭建

前言: vue2项目可谓十分常见,国内大部分的前端码农应该都是用vue2技术在开发,虽然vue3和react等技术也有很多,但是占据绝大多数的中高级搬砖码农应该干的都是vue2技术的项目,就算现在很多人转战vue3技术了,但是维护原有vue2的项目应该也是很多的。 我本来是不打算写vue2的技术…

用Go实现yaml文件节点动态解析

1.摘要 在大多数Go语言项目中, 配置文件通常为yaml文件格式, 在文件中可以设置项目中可灵活配置的各类参数, 通常这类参数都是比较固定的, 可以将其映射为对应的结构体在项目中进行使用, 如果需要调整参数时, 只需要增减结构体参数字段内容即可。 但同时还存在另外一种情况, …

文献阅读——Layered Costmaps for Context-Sensitive Navigation

摘要 许多导航系统,包括无处不在的ROS导航堆栈,在单个成本图上执行路径规划,其中大部分信息存储在单个网格中。这种方法在生成最小长度的无碰撞路径方面非常成功,但是当成本图中的值超出已占用或空闲空间时,它在动态的…

ATFX汇市:美国10月CPI数据来袭,通胀率料将进一步走低

ATFX汇市:本周二21:30,美国劳工部将公布10月未季调CPI年率,前值为3.7%,预期值3.3%;9月未季调核心CPI年率将于同一时间公布,前值为4.1%,预期值4.1%。机构预期美国名义通胀率将显著下降&#xff0…

EasyA正在帮助Sui为新一代Web3 App培养构建者

最近,我们采访了Phil和Dom Kwok,他们是兄弟也是Web3教育移动应用EasyA的共同创始人。这个教育app通过学习模块和编码挑战的形式,向开发人员教授有关不同区块链及其独特特性的知识。他们在十月初推出了他们的第一个Sui模块,并在随后…

Pikachu(皮卡丘靶场)初识XSS(常见标签事件及payload总结)

目录 1、反射型xss(get) 2、反射性xss(post) 3、存储型xss 4、DOM型xss 5、DOM型xss-x XSS又叫跨站脚本攻击,是HTML代码注入,通过对网页注入浏览器可执行代码,从而实现攻击。 ​ 1、反射型xss(get) Which NBA player do you like? 由…

hive更改表结构的时候报错

现象 FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compete_company_id in alter table statement 23/11/14 17:59:27 ERROR org.apache.hadoop.hive.ql.Driver: FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compe…