我创建了一个示例项目,以使用颜色显示视图布局。看起来是这样的:
下面是产生它的代码:
struct ContentView: View {
@State private var isShowingLeftPopup: Bool = false
@State private var isShowingRightPopup: Bool = false
var body: some View {
TabView {
VStack {
Spacer()
ZStack {
Color.red
.frame(height: 200)
HStack(spacing: 15) {
Color.accentColor
.disabled(self.isShowingRightPopup)
.onTapGesture {
self.isShowingLeftPopup.toggle()
}
Color.accentColor
.disabled(self.isShowingLeftPopup)
.onTapGesture {
self.isShowingRightPopup.toggle()
}
}
.frame(height: 70)
.padding(.horizontal)
}
Color.purple
.frame(height: 300)
}
}
}
}
点击两个蓝色矩形中的一个时,我想在蓝色矩形正下方的屏幕上制作一个视图动画,以填充蓝色矩形和底部的标签栏之间的垂直空间。目前,动画并不那么重要-我不知道如何将条件视图锚定到蓝色矩形的底部,并调整其大小以适合下面的剩余空间。
我整理了一下当点击左侧的蓝色矩形时的外观的样机:
在此示例中,我使用固定高度,但是我正在寻找不依赖固定值的解决方案。有人知道如何将绿色矩形锚定在蓝色矩形的底部,并动态调整其大小以填充垂直空间直至标签栏吗?
答案 0 :(得分:1)
您可以从GeometryReader,首选项和AnchorPreferences中受益。我写了很多有关它们的文章。要了解有关它们如何工作的更多信息,请参考它们:
GeometryReader文章: https://swiftui-lab.com/geometryreader-to-the-rescue/
首选项文章:https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
具体来说,对于您要完成的工作,您需要知道蓝色视图和紫色视图(指示绿色视图的下限)的大小和位置。一旦获得该信息,其余的工作就很容易了。下面的代码可以做到这一点:
import SwiftUI
struct MyData {
let viewName: String
let bounds: Anchor<CGRect>
}
struct MyPreferenceKey: PreferenceKey {
static var defaultValue: [MyData] = []
static func reduce(value: inout [MyData], nextValue: () -> [MyData]) {
value.append(contentsOf: nextValue())
}
typealias Value = [MyData]
}
struct ContentView: View {
@State private var isShowingLeftPopup: Bool = false
@State private var isShowingRightPopup: Bool = false
var body: some View {
TabView {
VStack {
Spacer()
ZStack {
Color.red
.frame(height: 200)
HStack(spacing: 15) {
Color.accentColor
.disabled(self.isShowingRightPopup)
.onTapGesture {
self.isShowingLeftPopup.toggle()
}
.anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
return [MyData(viewName: "leftView", bounds: $0)]
}
Color.accentColor
.disabled(self.isShowingLeftPopup)
.onTapGesture { self.isShowingRightPopup.toggle() }
.anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
return [MyData(viewName: "rightView", bounds: $0)]
}
}
.frame(height: 70)
.padding(.horizontal)
}
Color.purple
.frame(height: 300)
.anchorPreference(key: MyPreferenceKey.self, value: .bounds) {
return [MyData(viewName: "purpleView", bounds: $0)]
}
}.overlayPreferenceValue(MyPreferenceKey.self) { preferences in
GeometryReader { proxy in
Group {
if self.isShowingLeftPopup {
ZStack(alignment: .topLeading) {
self.createRectangle(proxy, preferences)
HStack { Spacer() } // makes the ZStack to expand horizontally
VStack { Spacer() } // makes the ZStack to expand vertically
}.frame(alignment: .topLeading)
} else {
EmptyView()
}
}
}
}
}
}
func createRectangle(_ geometry: GeometryProxy, _ preferences: [MyData]) -> some View {
let l = preferences.first(where: { $0.viewName == "leftView" })
let r = preferences.first(where: { $0.viewName == "rightView" })
let p = preferences.first(where: { $0.viewName == "purpleView" })
let bounds_l = l != nil ? geometry[l!.bounds] : .zero
let bounds_r = r != nil ? geometry[r!.bounds] : .zero
let bounds_p = p != nil ? geometry[p!.bounds] : .zero
return RoundedRectangle(cornerRadius: 15)
.fill(Color.green)
.frame(width: bounds_r.maxX - bounds_l.minX, height: bounds_p.maxY - bounds_l.maxY)
.fixedSize()
.offset(x: bounds_l.minX, y: bounds_l.maxY)
}
}