课程链接:https://www.bilibili.com/video/BV1q64y1d7x5?p=5
课程项目仓库:https://github.com/cnatom/MemorizeSwiftUI
扩展extension
swift可以通过extension
关键字为现有的class, strut, enum或者protocol添加特性,我们以常用的Array结构体为例来说明。
定义一个数组
var cards: Array[Int] = [1]
我们想要为Array扩展一个新的计算变量oneAndOnly
,只有当数组长度为1时,才会返回数组中唯一的变量,否则返回nil。
一般写法如下:
var oneAndOnly: Int? {
if cards.count == 1 {
return cards[0]
} else {
return nil
}
}
我们也可以通过扩展Array,使Array本身就带有上述功能。
extension Array {
var oneAndOnly: Element? { // 该计算变量的类型为泛型
if self.count == 1 {
return self.first // 返回第一个
} else {
return nil
}
}
}
扩展后,就可以直接调用计算变量oneAndOnly,极大的简化了主业务代码。
print(cards.oneAndOnly)
属性观察器Property Observers
此处节选自菜鸟教程
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。
可以为属性添加如下的一个或全部观察器:
willSet
在设置新的值之前调用didSet
在新的值被设置之后立即调用- willSet和didSet观察器在属性初始化过程中不会被调用
class Samplepgm {
var counter: Int = 0{
willSet(newTotal){
print("计数器: \(newTotal)")
}
didSet{
if counter > oldValue {
print("新增数 \(counter - oldValue)")
}
}
}
}
let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800
以上程序执行输出结果为:
计数器: 100
新增数 100
计数器: 800
新增数 700
View布局机制
HStack与VStack
Stack根据Views“灵活度”从低到高的顺序,依次提供空间
举个例子说明什么是灵活度:
-
Image
组件一般是固定大小,因此认为是“最不灵活的”。 -
Text
组件总是想调整大小以完全适合其文本,因此认为是“稍微灵活的”。 -
RoundedRectangle
组件总是使用提供的任何空间,因此被认为是“非常灵活的”。
因此,Stack提供空间的先后顺序:Image、Text、RoundedRectangle。
这个其实非常容易理解,RoundedRectangle想要霸占Stack中的所有剩余空间,当然需要等那些固定大小的View放在Stack里面才知道剩余空间有多大。因此,先为固定大小的View分配空间,再为RoundedRectangle分配空间。
我们可以使用.layoutPriority(100.0)
来更改组件的空间分配顺序,数值越大,优先级越高,分配次序越靠前。
HStack{
Text("我优先度被手动增大,先获得空间").layoutPriority(100.0) // 分配次序:1
Image("resource") // 分配次序:2 默认的layoutPriority为0
Text("我比Image灵活,我最后获得空间") // 分配次序:3
}
其他容器
-
LazyHStack
与LazyVStack
这类容器的大小与其包裹的子组件的大小一致,不会填充父容器的空间。
-
ScrollView
该容器会填充父容器的剩余空间,并提供一个可滚动的内部空间
-
ZStack
该容器根据子组件的大小调整容器大小。如果其子组件想要霸占所有剩余空间,那么ZStack也会霸占其父容器的剩余空间空间。
-
特殊的容器:修饰符
修饰符本身会返回一个试图。如
anyView.padding(10)
,生成一个内边距为10的包裹着anyView
的容器
布局实例
HStack {
CardView(card: Card).aspectRatio(2/3, contentMode: .fit)
}
.foregroundColor(Color.orange)
.padding(10)
- 由
.padding(10)
修饰符生成最外层容器。 .foregroundColor(Color.orange)
将其属性传入其内部HStack
容器放在.padding(10)
生成的容器内.aspectRatio(2/3, contentMode: .fit)
生成一个宽度与HStack相同,纵横比为2/3的容器,放在HStack
内- 最后将
CardView
组件放在.aspectRatio
生成的容器中
获取父组件大小
使用SwiftUI提供的GeometryReader
组件可以获取父组件的大小
......
var body: some View{
GeometryReader{ geometry in
// geometry.size : 获取父组件的大小
// geometry.size.height : 父组件的高度
// geometry.size.width : 父组件的宽度
Text(card.content).font(font(size: geometry.size)) // emoji的大小会根据容器宽度的大小变化
}
}
......
private func font(size: CGSize) -> Font {
Font.system(size: min(size.height,size.width) * 0.5)
}
自定义View
我们可以使用@ViewBuiler
创建一个View列表
比如一个返回类型为some View
的函数
@ViewBuilder
func front(of card: Card) -> some View{
let shape = RoundedRectangle(cornerRadius: 20)
shape
shape.stroke()
Text(card.content)
}
还可以创建一个struct视图
struct MyContentView<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.padding()
.cornerRadius(10)
}
}