需求:
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 等信息,用这个方法把信息发送出去,类似与对应UIKit的postNotification。
二:.onPreferenceChange(key:perform:)
用途:
- 用于响应由.preference或.anchorPreference定义的偏好变化。
- 当偏好值发生变化时,.onPreferenceChange中的代码块将被执行,允许你根据偏好的变化进行相应的操作。
其实就是用来监听当value发生变化,这个方法会被响应,类似与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()并没有撑开,随意底部蓝色区域覆盖了红色的区域。
这是因为.preference()
取值出现问题,继续修改:
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中,
发现并不好使,具体原因还不知道,难道.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)//不能设置在这里
}
网友评论