SwiftUI - 界面布局知识点

news/2024/7/20 20:24:07 标签: swiftui, ios, swift
前言

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()。 


http://www.niftyadmin.cn/n/5179747.html

相关文章

AI技术如何融合应用于工业物联网

人工智能技术在近年来得到飞跃性地发展,在自主识别、分析、判断、规划等功能方面都进步显著,也已经应用于越来越多的行业产业。 在工业物联网领域,人工智能也将成为一大助力,通过与工业物联网系统集成融合,能够为工业…

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

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

中科驭数荣获北京市科学技术进步奖二等奖

近日,北京市人民政府发布京政发〔2023〕23号公告,2022年度北京市科学技术奖授奖清单揭晓,中国科学院计算技术研究所、北京控制工程研究所、中科驭数(北京)科技有限公司等产学研单位提报的“领域专用处理器关键技术及应…

【springboot】@restcontroller和@controller的区别

返回值不同:RestController注解的类中的所有方法都会返回JSON或XML等数据格式,而Controller注解的类中的方法可以返回JSP或HTML等视图页面。 默认注解不同:RestController注解中包含了ResponseBody注解,表示返回的数据会直接作为…

SMART PLC 和S7-1200PLC MODBUSTCP通信速度测试

SMART PLC MODBUSTCP通信详细介绍请参看下面文章链接: S7-200SMART PLC ModbusTCP通信(多服务器多从站轮询)_matlab sumilink 多个modbustcp读写_RXXW_Dor的博客-CSDN博客文章浏览阅读6.4k次,点赞5次,收藏10次。MBUS_CLIENT作为MODBUS TCP客户端通过S7-200 SMART CPU上的…

python数据结构与算法-00_课程简介之笨方法学算法

什么是算法和数据结构? 你可能会在一些教材上看到这句话: 程序 算法 数据结构 算法(Algorithm):是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解…

每日一练 | 华为认证真题练习Day129

1、在华为AR路由器中,缺省情况下OSPF协议优先级的数值为? A. 20 B. 10 C. 30 D. 0 2、在广播型的接口上配置静态路由时,必须要指定下一跳地址。 A. 对 B. 错 3、在OSPF广播网络中,一台DRother路由器会与哪些路由器交换链路状…

[Linux]Linux网络

一、网卡指令——ifconfig ifconfig命令来自于net-tools包 (图形化界面已安装,而最小化界面是没有的。需要自己安装) #查看jet-tools是否安装 rpm -qi net-tools ifconfig用法 ifconfig默认查看活跃网卡-a查看所有网卡ifconfig网卡名查看…