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

8、infinity 

使用它来让视图占有最大宽度或高度

HStack{
         Text("0.000").frame(maxWidth: .infinity)
      }

这个.infinity会让text的背景撑满整个父视图的宽度,虽然它的实际内容宽度只有0.000的文字宽度,但是在一些需要撑开布局的地方可以用到它

SwiftUI is a new framework introduced by Apple in 2019 for building user interfaces on all Apple platforms (iOS, iPadOS, macOS, watchOS, and tvOS). It provides a new way of declaratively building user interfaces using Swift code. Objective-C, on the other hand, is an object-oriented programming language that has been widely used for building Mac and iOS applications. It has been around for much longer than SwiftUI, and has a large and established developer community. Here are some key differences between the two: 1. Syntax: SwiftUI has a simpler and more intuitive syntax compared to Objective-C, which can be more verbose and difficult to read. 2. Declarative vs Imperative: SwiftUI is a declarative framework, which means you describe what your user interface should look like, and the framework takes care of rendering it on the screen. Objective-C, on the other hand, is an imperative language, which requires you to write code to manipulate the user interface elements directly. 3. Interoperability: SwiftUI and Objective-C can coexist in the same project, and it is possible to use SwiftUI components in Objective-C code. However, the reverse is not true, as Objective-C code cannot be used in SwiftUI. 4. Learning Curve: SwiftUI is generally considered easier to learn for new developers, whereas Objective-C has a steeper learning curve. Ultimately, the choice between SwiftUI and Objective-C depends on the specific needs and preferences of each developer and the project they are working on. Both frameworks have their strengths and weaknesses, and both can be used to build high-quality and performant applications.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值