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

opacity()

用于设置某个 View 的透明度。

struct ContentView: View {
    
    var body: some View {
        Text("圆角矩形")
            .padding()
            .background(Color.red)
            .cornerRadius(25)
            .opacity(0.5) //透明度
    }
}

compositingGroup()

将修饰的内容组合以后再产生后续的效果。

struct ContentView: View {
    
    var body: some View {
        ZStack {
            Circle().fill(Color.red)
            
            Circle().fill(Color.green).offset(x: -40, y: 75)
            
            Circle().fill(Color.blue).offset(x: 40, y: 75)
        }
            .compositingGroup() //可以比较一下加与不加的区别
            .opacity(0.5)
            .frame(width: 160, height: 160)
    }
}

colorInvert()

用于颜色取反,例如原先是白色,取反后是黑色。

struct ContentView: View {
    
    var body: some View {
        ZStack {
            Circle().fill(Color.red)
            
            Circle().fill(Color.green).offset(x: -40, y: 75)
            
            Circle().fill(Color.blue).offset(x: 40, y: 75)
        }
        .compositingGroup()
        .opacity(0.5)
        .frame(width: 160, height: 160)
        .colorInvert() //反转颜色
    }
}

id()

为 View 绑定一个唯一标识符,当其改变时,表面上看,该 View 恢复到初始状态,其本质是创建了一个新的 View。需要注意的是待重置的 View 必须是一个独立封装的 View。

import SwiftUI

//封装一个独立的View
struct TextFieldGroup: View {
    
    @State private var text0 = ""
    @State private var text1 = ""
    @State private var text2 = ""
    @State private var text3 = ""
    @State private var text4 = ""
    
    var body: some View {
        VStack {
            TextField("text0", text: $text0)
            TextField("text1", text: $text1)
            TextField("text2", text: $text2)
            TextField("text3", text: $text3)
            TextField("text4", text: $text4)
        }
        .padding(.horizontal)
        .textFieldStyle(RoundedBorderTextFieldStyle())
    }
}

struct ContentView: View {
    
    @State private var textFieldId = 0
    
    var body: some View {
        VStack {
            TextFieldGroup()
                .id(textFieldId) //绑定id
            Button("重置") {
                self.textFieldId += 1 //改变id的值
            }
        }
    }
}

亮度、色彩、饱和度、对比度

struct ContentView: View {
    
    var body: some View {
        Image("jobs")
            .brightness(0.1) // 亮度
            .colorMultiply(.red) // tint为红色
            .saturation(0.5) // 饱和度
            .contrast(0.5) // 对比度
    }
}

layoutPriority()

layoutPriority 用于控制布局的优先级,让父 View 优先对某个子 View 进行布局。在默认情况下,布局优先级都为 0。

举例来说,运行下面的代码,此时 Image 和第 1 个 Text 内容显示完整,第 2 个 Text 内容被截断。

struct ContentView: View {
    
    var body: some View {
        HStack {
            Image(systemName: "person.circle")
            
            Text("站长介绍:")
                .background(Color.red)
            
            Text("熟悉iOS、Swift等单词的拼写")
                .background(Color.green)
        }
        .lineLimit(1)
        .frame(width: 300)
    }
}

如果此时希望优先考虑第 2 个 Text,让它的内容优先显示,就可以用 layoutPriority。

struct ContentView: View {
    
    var body: some View {
        HStack {
            Image(systemName: "person.circle")
            
            Text("站长介绍:")
                .background(Color.red)
            
            Text("熟悉iOS、Swift等单词的拼写")
                .layoutPriority(1.0) //优先显示
                .background(Color.green)
        }
        .lineLimit(1)
        .frame(width: 300)
    }
}

Modifier 自定义

前面我们解析了很多系统预置的修饰符(Modifier)。实际上我们也可以自己通过组合系统预置的 Modifier 来自定义。另外,当很多 View 有共性的 Modifier(如共同的 background、padding、font 等),此时可以通过自定义 Modifier 进行封装,避免重复,使代码更简洁。

要自定义 Modifier,首先我们要新建一个结构体遵守 ViewModifier 协议,实现 body(content: ) 方法,然后将需要的 Modifier 修饰到 content 参数上。

import SwiftUI

struct DIYModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .background(Color.red)
            .foregroundColor(Color.white)
            .font(.largeTitle)
            .padding()
    }
}

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

自定义的 Modifier优先级是最低的,如果某个 View的修饰符和自定义 Modifier重复了,即使自定义 Modifier放在 View的最后修饰也不会起作用。

Modifier 的调用顺序

在 View 和 Modifier 解析的系列文章最后,我们来讨论一下 Modifier 的调用顺序问题。也许读者在之前使用的过程中碰到了将两个 Modifier 顺序调换后得到完全不同的效果的现象,在本节我们就着重探讨一下。

判断 Modifier 调用是否要关心顺序其实非常简单,有以下两条原则:

● 若调用 Modifier 后返回的仍然是这个 View 本身,这类 Modifier 的调用不必关心顺序,并且我们称它们为“原地 Modifier”。典型的例子有 font、bold、foregroundColor、italic 等。
● 若调用的 Modifier 将原来的 View 进行包装并返回新的 View,则这类 Modifier 我们往往要关心调用的顺序,并且我们称它们为“封装类 Modifier”。典型的例子有 padding、background 等。

这两点如何判断呢?我们可以借助 Xcode 的自动补全来判断。以下图为例:

我们可以看到 .font 的自动补全提示的返回类型仍然为 Text,那么我们就可以说 .font 是一个“原地 Modifier”,调用时不必关心顺序。

再来看看 .padding 的情况:

显然 .padding 提示返回的类型是一个 View 而不是原来的 Text,那么我们就可以说 .padding 是一个“封装类 Modifier”,调用时要关心顺序,否则会得到错误的结果。

若某个 Modifier的自动补全中既符合原地 Modifier的条件又符合封装类 Modifier的条件,我们视其为原地 Modifier。

为什么说封装类 Modifier 调用时要关心顺序呢?我们来看下面这个例子。

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

若无意外,这段程序应该是能顺利运行的。如果我们把第 5 行和第 6 行对调一下会发生什么呢?

struct ContentView: View {
    
    var body: some View {
        Text("Hello, SwiftUI")
            .padding()
            .bold() //Error: Value of type 'some View' has no member 'bold'
    }
}

程序报错,因为 View 中没有 bold 这个类型。其实也非常好理解:Text 可以理解为 View 的子类,View 中有的属性 Text 肯定都有,而 Text 中有的属性 View 中则不一定有。上面的例子中,先对 Text 使用 padding,返回的已经是一个 View 了,自然也就没有 “bold” 这个 Text 独有的修饰符了。

为了更加深入理解这个特性,我们再举一个例子。

struct ContentView: View {
    
    var body: some View {
        Text("+")
            .font(.title) // 1
            .foregroundColor(.white) // 2
            .padding() // 3
            .background(Color.orange) // 4
    }
}

读者可以尝试替换注释 3 和注释 4 两行代码的位置,看看会有什么变化,并结合上面的讨论思考一下出现这个变化的原因。

发表回复

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