需求:
body要求設置一個最小高度minHeight,body內的content進行垂直排列VStack。且當content內容較少時魔慷,置頂展示。
問題1:
VStack中無法設置alignment: .top
問題2:
使用Spacer()會導致垂直距離著恩,直接被撐開院尔。無法滿足minHeight的要求蜻展。
處理方法:
所以為了使用Spacer()
,就要直接計算出content的高度邀摆,將body的height設置為:max(content高度纵顾,minHeight)
一:.preference(key:defaultValue:transform:)
用途:
- 用于定義一種偏好(preference),可以在視圖層次結構中傳遞特定類型的值栋盹。
- 通過在視圖樹中的不同位置設置和讀取偏好值施逾,可以實現跨視圖的信息傳遞和響應。
其實就是在你想在視圖樹中哪個位置獲取height or size 等信息例获,用這個方法把信息發(fā)送出去汉额,類似與對應UIKit的postNotification。
二:.onPreferenceChange(key:perform:)
用途:
- 用于響應由.preference或.anchorPreference定義的偏好變化榨汤。
- 當偏好值發(fā)生變化時蠕搜,.onPreferenceChange中的代碼塊將被執(zhí)行,允許你根據偏好的變化進行相應的操作收壕。
其實就是用來監(jiān)聽當value發(fā)生變化妓灌,這個方法會被響應,類似與UIKit中的addNotification蜜宪。
所以我們有了下面的代碼
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct ContentView: View {
@State private var vstackHeight: CGFloat = 0
VStack {
VStack(spacing: .zero) {
VStack(spacing: .zero) {
ForEach(0..<5) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
.frame(maxWidth:.infinity, alignment:.leading)
.background(GeometryReader { geo in
Color.red.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
}
}
Spacer()
}
.background {
Color.purple
}
.frame(height: max(300, vstackHeight))
.onPreferenceChange(HeightPreferenceKey.self) { height in
vstackHeight = height
}
Text("---jlifedkljdfsjkldskjldfjkdfjksdjjlifedkljdfsjkldskjldfjkdfjksdjjlifedkljdfsjkldskjldfjkdfjksdjjlife---")
.foregroundStyle(Color.gray)
.font(.system(size: 18))
.padding(Constants.spacing10)
.background(Color.blue)
Spacer()
}
.background {
Color.black
}
}
}
Simulator Screenshot - iPhone 15 Pro - 2024-09-15 at 12.28.48.png
當內容過多時虫埂,出現文字顯示不全的問題
于是給Text()添加fixedSize屬性
.fixedSize(horizontal: false, vertical: true)
即:
ForEach(0..<5) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
.frame(maxWidth:.infinity, alignment:.leading)
.fixedSize(horizontal: false, vertical: true) // 添加這個屬性
.background(GeometryReader { geo in
Color.red.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
}
Simulator Screenshot - iPhone 15 Pro - 2024-09-15 at 12.29.26.png
添加.fixedSize()
屬性后,內容雖然顯示完整了圃验,但又能看到掉伏,外層的VStack()并沒有撐開,隨意底部藍色區(qū)域覆蓋了紅色的區(qū)域澳窑。
這是因為.preference()
取值出現問題斧散,繼續(xù)修改:
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
// value = nextValue()
value += nextValue()
}
}
Simulator Screenshot - iPhone 15 Pro - 2024-09-15 at 12.29.42.png
完整代碼為:
import SwiftUI
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
// value = nextValue()
value += nextValue()
}
}
struct ContentView: View {
@State private var vstackHeight: CGFloat = 0
var body: some View {
VStack(spacing: .zero) {
VStack(spacing: .zero) {
VStack(spacing: .zero) {
ForEach(0..<3) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
.frame(maxWidth:.infinity, alignment:.leading)
.fixedSize(horizontal: false, vertical: true) // 添加這個屬性
.background(GeometryReader { geo in
Color.clear.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
}
}
if vstackHeight < 300 { Spacer() }
}
.background {
Color.purple
}
.frame(height: max(300, vstackHeight))
.onPreferenceChange(HeightPreferenceKey.self) { height in
vstackHeight = height
}
Text("---jlifedkljdfsjkldskjldfjkdfjksdjjlifedkljdfsjkldskjldfjkdfjksdjjlifedkljdfsjkldskjldfjkdfjksdjjlife---")
.foregroundStyle(Color.gray)
.font(.system(size: 18))
.padding(Constants.spacing10)
.background(Color.blue)
Spacer(minLength: 0)
}
.background {
Color.black
}
}
}
思考如下代碼中將:
Color.clear.preference(key: HeightPreferenceKey.self, value: geo.size.height)
直接放置在外層VStack中,
發(fā)現并不好使照捡,具體原因還不知道,難道.preference()
不能放置在外層VStack()
中话侧?
原諒樓主也是一個小白而已栗精。。瞻鹏。有知道的請下面評論悲立,非常感謝~
struct HeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
// value += nextValue()
}
}
VStack(spacing: .zero) {
ForEach(0..<3) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
.frame(maxWidth:.infinity, alignment:.leading)
.fixedSize(horizontal: false, vertical: true) // 添加這個屬性
// .background(GeometryReader { geo in
// Color.clear.preference(key: HeightPreferenceKey.self, value: geo.size.height)
// })
}
}
.background(GeometryReader { geo in
Color.clear.preference(key: HeightPreferenceKey.self, value: geo.size.height)
})
New: 上面之所以會有問題,是因為對frame的height獲取后新博,又對superview的height設置薪夕,導致重新約束布局,所以會引起問題赫悄。
法一: 通過獲取到的height原献,當height < minHeight時:對內部整體的view做一個y軸的offset馏慨。
.offset(x: 0, y: vstackHeight < 300 ? -(300 - vstackHeight) / 2.0 : 0)
法二:就是通過ZStack的alignment,進行約束控制
其中VStack{}.frame(minHeight: 300)
是為了撐開ZStack姑隅,這樣就能使content安裝.top對齊
ZStack(alignment: .top) {
VStack{}.frame(minHeight: 300)
VStack(spacing: .zero) {
ForEach(0..<1) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
}
}
}
注意不能直接設置在下面這里写隶,因為這樣VStack又被撐開了,內部還是會按VStack居中布局讲仰。慕趴。。
ZStack(alignment: .top) {
VStack(spacing: .zero) {
ForEach(0..<1) { tag in
Text("------TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText=========")
.foregroundStyle(Color.gray)
.font(.system(size: 14))
.background{Color.yellow}
.padding(Constants.spacing10)
.frame(minHeight: Constants.spacing20)
}
}
.frame(minHeight: 300)//不能設置在這里
}