SwiftUI 学习笔记(二):常见的 View 和 Modifier 解析(一)

与 UIKit 使用多种多样的控件布局不同,SwiftUI 的布局主要是使用各种 View(视图) 和 Modifier(修饰符) 来设置页面布局。在 UIKit 中,我们使用控件的各种属性来构建 UI,而 SwiftUI 中,我们用 Modifier 来修饰 View 来达到同样的目的。实际上,大部分 SwiftUI 中的 View 和 UIKit 中的控件是有对应关系的,我们在学习的时候只要掌握好这种对应关系即可。本文开始就梳理一下 SwiftUI 中常见的 View 和 Modifier。

Text

Text 即为显示文字的 View,对应 UIKit 中的 UILabel。

struct ContentView: View {
    var body: some View {
        Text("Hello SwiftUI")
            .font(.title) //字体大小
            .foregroundColor(.orange) //字体颜色
            .bold() //加粗
            .italic() //斜体
            .underline() //下划线
            .lineLimit(2) //行数,若为nil则不限行数
    }
}

如果说我们有多行文本,可以通过 .multilineTextAlignment 修饰符来设置多行文本之间的对齐方式。

struct ContentView: View {
    var body: some View {
        Text("Hello\nSwiftUI")
            .multilineTextAlignment(.center)
    }
}

在以往的 UIKit 中,我们若想要实现将不同样式的文本拼接在一起,就必须用到 NSAttributedString。但在 SwiftUI 中就非常容易,只要把两个 Text 用“+”拼接起来即可。

struct ContentView: View {
    var body: some View {
        Text("Hello ")
            .foregroundColor(.red) +
        Text("SwiftUI")
            .bold()
            .italic()
    }
}

需要注意的是,第四行的加号不能省略,否则就违反了上一篇文章所讨论的 “返回 some修饰的协议时返回值类型必须确定”。

当 Text 中的文本特别长时,我们可以设置一些截断方式。

struct ContentView: View {
    
    var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。"
    
    var body: some View {
        //长文本
        Text(self.name)
            .frame(width:160, height: 50)
            //.truncationMode(.head) //头部截断
            //.truncationMode(.middle) //中部截断
            //.truncationMode(.tail) //默认尾部截断
    }
}

同时,我们可以通过设置 .minimumScaleFactor 修饰符,在当 Text 文本显示不下的时候通过缩小字号来使得文字显示全。

struct ContentView: View {
    
    var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。"
    
    var body: some View {
        //长文本
        Text(self.name)
            .frame(width:160, height: 50)
            .minimumScaleFactor(0.5) //括号里的参数指定该View允许的最小文本缩放比例,会根据情况自动调整
    }
}

通过使用 lineSpacing 来控制多行文本之间的行间距。

struct ContentView: View {
    
    var name: String = "SwiftUI是一门非常新的技术,我们一定要好好学习。"
    
    var body: some View {
        
        Text(self.name)
            .frame(width:160, height: 70)
            .lineSpacing(20)
    }
}

若我们要控制文本中的字符间距,可以使用 tracking 或 kerning 修饰符。tracking 会拉开连字符,而 kerning 不会;kerning 会留下一些尾随空白,而 tracking 不会。

struct ContentView: View {
    var body: some View {
        VStack {
            //显示为 f fi
            Text("ffi")
                .font(.custom("AmericanTypewriter", size: 70))
                .kerning(50)
            //显示为 f f i
            Text("ffi")
                .font(.custom("AmericanTypewriter", size: 70))
                .tracking(50)
        }
    }
}

padding()

padding 用于设置一个 View 周围的填充。在不设置参数的情况下,可以获得所有方向上系统提供的默认填充。

struct ContentView: View {
    var body: some View {
        Text("Hello SwiftUI")
            .padding()
    }
}

当然我们也可以自定义填充的方向和大小。

struct ContentView: View {
    var body: some View {
        Text("Hello SwiftUI")
            .padding(.bottom, 100)
    }
}

frame()

和 UIKit 类似,frame 修饰符用于显式指定一个 View 的大小(但是不能指定 View 的坐标了)。我们可以直接在构造函数中传入 width 和 height 指定 View 的宽高,也可以传入 minWidth、idealWidth、maxWidth、minHeight、idealHeight、maxHeight 来进行更加个性化的设置(若不指定就采用系统默认值)。还可以根据需要设置对齐方式 alignment。

struct ContentView: View {
    var body: some View {
        Text("666")
            .frame(width: 100, height: 50, alignment: .bottomLeading) //文字设在100x50区域的左下角
    }
}

Shape

Shape 是 SwiftUI 中新增的一个 View,用来表示形状。

struct ContentView: View {
    var body: some View {
        VStack {
            //矩形
            Rectangle()
                .foregroundColor(Color.red)
                .padding()
            //圆角矩形
            RoundedRectangle(cornerSize: CGSize(width: 10,height: 10))
                .foregroundColor(Color.blue)
                .frame(height:200)
            //圆形
            Circle()
                .foregroundColor(Color.yellow)
            //椭圆
            Ellipse()
                .foregroundColor(Color.pink)
                .frame(height:200)
            //胶囊型
            Capsule()
                .fill(Color.green)
                .frame(width: 200, height: 50)
        }
    }
}

还记得当初做红家面试题的时候,题目要求是将一个正方形切圆。在 SwiftUI 中,我们可以使用 .clipShape 修饰符轻松实现:

struct ContentView: View {
    var body: some View {
        Rectangle()
            .foregroundColor(Color.red)
            .frame(width: 100, height: 100)
            .clipShape(Circle())
    }
}

Color

颜色在 SwiftUI 中也是一种 View。

struct ContentView: View {
    var body: some View {
        Color.pink
            .frame(width: 200, height: 100)
            .padding()
    }
}

HStack

HStack 代表水平容器,用于处理若干个元素的横向摆放。在 HStack 的构造函数中我们可以传入两个属性:alignment 表示对齐方式、spacing 表示两两间距。

默认情况下,SwiftUI 会针对不同的 View设置默认间距,所以如果不需要,必须显式设为 0。

struct ContentView: View {
    var body: some View {
        HStack(alignment: .center, spacing: 20) {
            Color.red
            Color.blue
        }
    }
}

VStack

VStack 代表垂直容器,用于处理若干个元素的纵向摆放。和 HStack 相同,我们也可以在构造函数中传入 alignment 和 spacing 属性。

struct ContentView: View {
    var body: some View {
        VStack(alignment: .center, spacing: 20) {
            Color.red
            Color.blue
        }
    }
}

ZStack

ZStack 代表 Z 轴容器,可以理解为与屏幕垂直方向的容器。由于与屏幕垂直的方向只有前后之分,故 ZStack 没有 spacing 属性但是仍有 alignment 属性。

struct ContentView: View {
    var body: some View {
        ZStack (alignment: .center){
            Circle()
                .foregroundColor(Color.yellow)
                .frame(width: 50, height: 50, alignment: .center)
            Text("1")
        }
    }
}

HStack、VStack、ZStack 三种容器可以互相嵌套使用,例如下面这个例子。如果一切顺利,你可以在屏幕上看到一个用图形构建的人形。

struct ContentView: View {
    var body: some View {
        VStack (alignment: .center, spacing: 20){
            ZStack {
                Circle()
                HStack {
                    Rectangle()
                        .foregroundColor(Color.red)
                        .frame(width: 50, height: 50)
                        .padding()
                    Rectangle()
                        .foregroundColor(Color.red)
                        .frame(width: 50, height: 50)
                        .padding()
                }
            }
            HStack {
                Capsule()
                    .frame(width: 80)
                Rectangle()
                Capsule()
                    .frame(width: 80)
            }
            HStack {
                RoundedRectangle(cornerRadius: 10)
                    .frame(width: 100)
                RoundedRectangle(cornerRadius: 10)
                    .frame(width: 100)
            }
        }
    }
}

zIndex()

zIndex 为 ZStack 的 Z 轴索引。ZStack 中的 View 是叠加显示的,写在后面的 View 会遮住前面的,可以使用 zIndex 设置。View 的索引,索引大的就会显示在前面。

struct ContentView: View {
    
    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 30, style: .continuous)
                .frame(width: 300, height: 200)
                .foregroundColor(.red)
                .zIndex(2)
            
            RoundedRectangle(cornerRadius: 30, style: .continuous)
                .frame(width: 300, height: 200)
                .foregroundColor(.blue)
                .zIndex(1)
        } //此时红色圆角矩形在蓝色圆角矩形的上面
    }
}

Spacer

Spacer 是一个灵活的空间,可大可小,可以智能根据上下文自动设置大小完成空间的填充。

struct ContentView: View {
    var body: some View {
        VStack {
            Color.red
            //空隙(灵活的空间)
            Spacer()
            Color.blue
        }.frame(height: 300)
    }
}

可以通过 frame 修饰符来指定 Spacer 的大小。

struct ContentView: View {
    var body: some View {
        VStack {
            Color.red
            //指定Spacer的大小
            Spacer()
                .frame(height:100)
            Color.blue
        }.frame(height: 300)
    }
}

Divider

Divider 会显示一条分隔线用于分隔内容,其中 VStack 里面的分隔线是水平的、HStack 里面的分隔线是垂直的。

struct ContentView: View {
    var body: some View {
        VStack {
            Color.red
            //水平分隔线
            Divider()
            Color.blue
        }.frame(height: 300)
    }
}
struct ContentView: View {
    var body: some View {
        HStack {
            Color.red
            //垂直分隔线
            Divider()
            Color.blue
        }.frame(height: 300)
    }
}

Image

Image 即为显示图片的 View,对应 UIKit 中的 UIImageView。

struct ContentView: View {
    var body: some View {
        VStack {
            Image("666") //资源图片,需要将图片预先放在工程的Assets.xcassets文件夹中
            Image(systemName: "clock.fill") //系统图片
        }
    }
}

关于系统图片,我们可以前往 Apple 开发者网站 https://developer.apple.com/sf-symbols 下载 sf-symbols APP 来查询某个系统图片的名称。

每个图标下面的文字就是这个图标对应的 systemName 字符串

设置 forgroundColor 修饰符,我们可以改变渲染系统图片的颜色。有的时候系统图片可能默认被渲染成蓝色,我们可用 renderingMode(.original) 修饰符渲染成原图。

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "clock")
                .foregroundColor(.red)
            Image(systemName: "clock")
                .renderingMode(.original)
        }
    }
}

设置 frame、font 或 imageScale 修饰符,我们可以控制 Image 的大小。其中 font 既可以选择系统预置的大小(比如 .largeTitle、.footnote 等),也可以通过传入 .system(size: ) 的方法传入字号来设置图片大小;而 imageScale 只有三种大小——large、medium、small。

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "clock")
                .foregroundColor(.red)
                .font(.system(size: 30)) //直接输入字号
//                .font(.largeTitle) //使用系统预置大小
                .padding()
            Image(systemName: "clock")
                .foregroundColor(.red)
                .imageScale(.large)
        }
    }
}

特别地,若我们要用 frame 修饰符来改变图片的大小,我们必须先用 .resizable 修饰符来修饰 Image。

struct ContentView: View {
    var body: some View {
        Image(systemName: "clock")
            .resizable() //一定要有resizable,否则指定frame是无效的
            .foregroundColor(.red)
            .frame(width: 20, height: 20) //指定frame
            .padding()
    }
}

默认情况下,SwiftUI中图片绘制与 frame是无关的,而只会遵从图片本身的大小。如果我们想要图片可以按照所在的 frame缩放,需要添加 resizable()修饰符。

Image 有三种设置显示模式的修饰符——aspectRatio、scaledToFill、scaledToFit。其中 aspectRatio 的第一个参数用于控制图片显示的比例,第二个参数 contentMode 用于控制图片显示是否铺满屏幕(.fill 是充满屏幕、.fit 是根据屏幕大小自适应)。若我们不需要控制图片显示的比例,我们也可以直接使用 scaledToFill、scaledToFit 来设置是否需要铺满屏幕。

struct ContentView: View {
    var body: some View {
        Image(systemName: "clock")
            .resizable() //这边同样要先加resizable修饰符
            
            //图片充满整个屏幕,下面两种写法是等价的
//            .aspectRatio(contentMode: .fill)
//            .scaledToFill()
            
            //图片大小自适应,下面两种写法是等价的
//            .aspectRatio(contentMode: .fit)
//            .scaledToFit()
        
            .aspectRatio(2, contentMode: .fit) //启用第一个参数,设置长宽比
    }
}

我们可以设置一个 Image 为其它 View 的背景。

struct ContentView: View {
    var body: some View {
        Text("Hello SwiftUI")
            .font(.largeTitle)
            .background(
                Image(systemName: "clock")
                    .resizable()
                    .foregroundColor(.red)
                    .frame(width: 100, height: 100))
    }
}

Image 同样支持 .clipShape 修饰符来切割图片的形状。

struct ContentView: View {
    var body: some View {
        Image(systemName: "exclamationmark.square.fill")
            .resizable()
            .frame(width: 100, height: 100)
            .clipShape(Circle()) //切圆
    }
}

下面的代码综合运用了本文的一些知识,设置了一张图片的大小并将其居中屏幕显示。

struct ContentView: View {
    var body: some View {
        VStack(alignment:.leading,spacing: 0){
            Image(systemName: "exclamationmark.square.fill")
                .resizable()
                .scaledToFit()
                .frame(
                    //最大宽度
                    maxWidth: UIScreen.main.bounds.width,
                    //最大高度
                    maxHeight: 300,
                    alignment: .center
            )
        }
    }
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注