1.List
下圖所示的List
效果办素,在SwiftUI中,實(shí)現(xiàn)列表沒有OC和Swift那樣那么復(fù)雜,幾行代碼就實(shí)現(xiàn)了上述2種語言需要10幾行甚至更多行數(shù)的效果宙彪。
//
// BoxListView.swift
// BoxSwiftUI
//
// Created by BoxJing on 2023/6/13.
//
import SwiftUI
struct BoxListView: View {
@State private var students = Student.examples
var body: some View {
VStack(alignment: .leading) {
navBarView
List($students, id: \.name, editActions: .all) { student in
studentView(student.wrappedValue)
}
.listStyle(.plain)
.padding(.horizontal)
}
.safeAreaInset(edge: .bottom,alignment: .trailing, content: {
Image(systemName: "plus.circle.fill")
.font(.system(size: 66))
.padding(.trailing)
.symbolRenderingMode(.palette)
.foregroundStyle(.white, Color.accentColor.gradient)
})
}
}
extension BoxListView {
var navBarView: some View {
HStack {
Label("學(xué)生列表", systemImage: "person")
.padding()
.foregroundColor(.accentColor)
Spacer()
EditButton().buttonStyle(.bordered)
.padding(.trailing)
.environment(\.locale, .init(identifier: "zh-Hans"))
}
}
func studentView(_ student: Student) -> some View {
HStack {
Text(student.name)
Spacer()
Text(student.age.formatted())
}
}
}
struct BoxListView_Previews: PreviewProvider {
static var previews: some View {
BoxListView()
}
}
2. AnyLayout
有時(shí)候我們會(huì)遇到2種不同的布局情況,這時(shí)候可以用AnyLayout
來實(shí)現(xiàn):
struct BoxDifferentLayout: View {
@State var num:String = ""
var body: some View {
TextField("請輸入文字", text: $num)
Spacer()
let shouldUseVStack = (num.count % 2 == 0)
let layout = shouldUseVStack ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout {
Text("文字1")
Text("文字2")
}
}
}
當(dāng)輸入的文字長度為奇數(shù)時(shí)候2個(gè)文字會(huì)橫向排列瘸爽,當(dāng)文字長度為偶數(shù)時(shí)候2個(gè)文字會(huì)垂直排列您访。
3. GeometryReader
當(dāng)我們需要獲取位置信息時(shí),我們在SwiftUI中可以使用GeometryReader
剪决,它包含著一個(gè)視圖View的所有位置信息灵汪,會(huì)返回一個(gè)GeometryProxy實(shí)例,這使我們能夠訪問其容器的大小和坐標(biāo)柑潦。
var body: some View {
GeometryReader { proxy in
// proxy 里面包含所有的位置信息(x,y,width,height)
}
}
GeometryReader仍然需要我們在它的主體中聲明一個(gè)視圖享言,我們可以使用Color.clear創(chuàng)建一個(gè)隱形層:
var body: some View {
customView
.background(
GeometryReader { proxy in
Color.clear
}
)
}
4. PreferenceKey
SwiftUI提供了PreferenceKey的功能,這是SwiftUI通過視圖樹傳遞信息的方式渗鬼。
我們開始自定義一個(gè)PreferenceKey,即SizePreferenceKey:
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
PreferenceKey
是一個(gè)通用協(xié)議览露,需要一個(gè)靜態(tài)函數(shù)和一個(gè)靜態(tài)默認(rèn)值:
defaultValue
是被使用,當(dāng)視圖沒有該屬性的顯式值時(shí)
reduce(value:nextValue:)
將渲染樹中找到的value值與新的value值組合在一起譬胎。
我們將使用PreferenceKey來存儲(chǔ)我們的子元素的測量大小差牛,回到我們的示例:
var body: some View {
childView
.background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
}
現(xiàn)在子視圖的大小就在渲染樹的層次結(jié)構(gòu)中了!我們怎么讀取呢?
SwiftUI提供了一個(gè)View的extension,onPreferenceChange(_:perform:)
它讓我們指定一個(gè)PreferenceKey堰乔,當(dāng)該P(yáng)referenceKey發(fā)生變化時(shí)就會(huì)觸發(fā)回調(diào)方法:
var body: some View {
childView
.background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self) { newSize in
print("The new child size is: \(newSize)")
}
}
由于onPreferenceChange
偏化,任何對此PreferenceKey
感興趣的父組件都可以提取值并在值更改時(shí)得到通知。
這種獲取子視圖大小的方法可以直接封裝為View的擴(kuò)展方便使用:
extension View {
func getViewSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { proxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: proxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
在使用的時(shí)候就方便很多:
var body: some View {
BoxView
.getViewSize { size in
print("Size is: \(size)")
}
}