Big*_*air 4 ios swiftui geometryreader swiftui-scrollview
基于这个问题:SwiftUI - Drawing (curved) paths between view
我使用 绘制所需的视图和路径GeometryReader
。问题是,现在屏幕变得太长而无法显示全部内容(有 15 个这样的视图,但您在这里只能看到 6 个):
所以...我ScrollView
在整个事情上添加了一个。之外,GeometryReader
因为否则所有观点都是错误的。这是代码:
var body: some View {
if (challengeViewModel.challengeAmount != 0) {
ScrollView {
GeometryReader { geometry in
let points = calculatePoints(geometry: geometry)
drawPaths(geometry: geometry, points: points)
.stroke(lineWidth: 3).foregroundColor(.yellow)
drawCircles(geometry: geometry, points: points)
}
}
.background(Color.black)
.navigationBarTitle("Journey")
} else {
Text("Loading...")
}
}
Run Code Online (Sandbox Code Playgroud)
(内容基本上就是1.根据屏幕尺寸计算点,2.沿着点绘制路径,3.在点上放置圆圈)
问题:
现在我明白需要从父级和子级GeometryReader
获取其高度,这成为一个“先有鸡还是先有蛋的问题”。ScrollView
所以我想手动计算高度GeometryReader
。
第一次尝试
最简单的方法是采用一些固定值并计算出一个非常粗略的值。我只是将其添加到GeometryReader
:
GeometryReader { geometry in
let points = calculatePoints(geometry: geometry)
drawPaths(geometry: geometry, points: points)
.stroke(lineWidth: 3).foregroundColor(.yellow)
drawCircles(geometry: geometry, points: points)
}
.frame(height: JourneyView.SPACING_Y_AXIS * CGFloat(1.2) * CGFloat(challengeViewModel.challengeAmount))
Run Code Online (Sandbox Code Playgroud)
这有效。但现在我的屏幕很长,底部有不必要的空间。根据手机型号的不同,它可能看起来完全不同。
第二次尝试
一种潜在的解决方案似乎是使用DispatchQueue.main.async
inside.body
并将@State
变量设置为其中计算出的儿童的高度。如此处所述:https ://stackoverflow.com/a/61315678/1972372
所以:
@State private var totalHeight: CGFloat = 100
var body: some View {
if (challengeViewModel.challengeAmount != 0) {
ScrollView {
GeometryReader { geometry in
let points = calculatePoints(geometry: geometry)
drawPaths(geometry: geometry, points: points)
.stroke(lineWidth: 3).foregroundColor(.yellow)
drawCircles(geometry: geometry, points: points)
}
.background(GeometryReader { gp -> Color in
DispatchQueue.main.async {
// update on next cycle with calculated height of ZStack !!!
print("totalheight read from Geo = \(totalHeight)")
self.totalHeight = gp.size.height
}
return Color.clear
})
}
.background(Color.black)
.frame(height: $totalHeight.wrappedValue)
.navigationBarTitle("Journey")
} else {
Text("Loading...")
}
}
Run Code Online (Sandbox Code Playgroud)
这根本不起作用。由于某种原因,收到ScrollView
固定值 100 或 10 并且永远不会改变。我尝试将该.background(...)
块放置在任何一个子项上,以尝试将每个子项的值相加,但这只会创建一个无限循环。但它似乎有可能以某种方式解决问题。
第三次尝试
使用PreferenceKeys
类似于本教程或此答案: https: //stackoverflow.com/a/64293734/1972372。
这看起来确实应该有效。我ZStack
在里面放置了一个GeometryReader
可以抓取的视图,height
并添加了一个与之类似的.background(...)
:
.background(GeometryReader { geo in
Color.clear
.preference(key: ViewHeightKey.self,
value: geo.size.height
})
Run Code Online (Sandbox Code Playgroud)
一起.onPreferenceChange(...)
来吧。
但不幸的是,这是所有尝试中效果最差的,因为它根本没有改变,只在开始时以 10 的基值调用一次。我尝试将它放在所有子视图上,但只得到了奇怪的结果,所以我已经删除了代码那。
我现在觉得只是采用愚蠢但有效的第一次尝试解决方案,只是为了让我免受其他两个问题的困扰。
有人能看出我在尝试 2 或 3 中做错了什么以及为什么我没有收到我想要的结果吗?因为看起来他们应该工作。
根据要求,这是完整的课程(使用第一次尝试技术):
struct JourneyView: View {
// MARK: Variables
@ObservedObject var challengeViewModel: ChallengeViewModel
@State private var selectedChlgID: Int = 0
@State private var showChlgDetailVIew = false
// Starting points
private static let START_COORD_RELATIVE_X_LEFT: CGFloat = 0.2
private static let START_COORD_RELATIVE_X_RIGHT: CGFloat = 0.8
private static let START_COORD_RELATIVE_Y: CGFloat = 0.05
// Y axis spacing of chlg views
private static let SPACING_Y_AXIS: CGFloat = 120
// MARK: UI
var body: some View {
if (challengeViewModel.challengeAmount != 0) {
ScrollView {
GeometryReader { geometry in
let points = calculatePoints(geometry: geometry)
drawPaths(geometry: geometry, points: points)
.stroke(lineWidth: 3).foregroundColor(.yellow)
drawCircles(geometry: geometry, points: points)
}
.frame(height: JourneyView.SPACING_Y_AXIS * CGFloat(1.2) * CGFloat(challengeViewModel.challengeAmount))
NavigationLink("", destination: ChallengeView(challengeID: selectedChlgID), isActive: $showChlgDetailVIew)
.opacity(0)
}
.background(Color.black)
.navigationBarTitle("Journey")
} else {
Text("Loading...")
}
}
// MARK: Functions
init() {
challengeViewModel = ChallengeViewModel()
}
func calculatePoints(geometry: GeometryProxy) -> [CGPoint] {
// Calculated constants
let normalizedXLeft = JourneyView.START_COORD_RELATIVE_X_LEFT * geometry.size.width
let normalizedXRight = JourneyView.START_COORD_RELATIVE_X_RIGHT * geometry.size.width
let normalizedY = JourneyView.START_COORD_RELATIVE_Y * geometry.size.height
let startPoint = CGPoint(x: geometry.size.width / 2, y: normalizedY)
var points = [CGPoint]()
points.append(startPoint)
(1...challengeViewModel.challengeAmount).forEach { i in
let isLeftAligned: Bool = i % 2 == 0 ? false : true
let nextPoint = CGPoint(x: isLeftAligned ? normalizedXRight : normalizedXLeft,
y: normalizedY + JourneyView.SPACING_Y_AXIS * CGFloat(i))
points.append(nextPoint)
}
return points
}
func drawPaths(geometry: GeometryProxy, points: [CGPoint]) -> some Shape {
// Connection paths
Path { path in
path.move(to: points[0])
points.forEach { point in
path.addLine(to: point)
}
}
}
func drawCircles(geometry: GeometryProxy, points: [CGPoint]) -> some View {
let circleRadius = geometry.size.width / 5
return ForEach(0...points.count - 1, id: \.self) { i in
JourneyChlgCircleView(chlgName: "Sleep" + String(i), color: Color(red: 120, green: 255, blue: 160))
.frame(width: circleRadius, height: circleRadius)
.position(x: points[i].x, y: points[i].y)
.onTapGesture {
selectedChlgID = i
showChlgDetailVIew = true
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
首先:好问题!:)
似乎这里的很多混乱(问题、评论和现有答案之间)都是过度设计的问题。我制定的解决方案相当简单,并使用您现有的代码(不需要任何幻数)。
首先,创建一个新@State
变量。您可能知道,每次更新具有此属性包装器的变量时,SwiftUI 都会重新绘制您的视图。
@State var finalHeight: CGFloat = 0
Run Code Online (Sandbox Code Playgroud)
将变量的初始值设置为零。假设我们不知道您的对象将发布多少“挑战”,这可以安全地提供可GeometryReader
忽略不计的初始高度(允许您在需要时呈现加载状态)。
然后,将 的GeometryReader
框架高度值更改为之前定义的变量值:
ScrollView {
GeometryReader { proxy in
// Your drawing code
}
.frame(height: finalHeight)
}
Run Code Online (Sandbox Code Playgroud)
最后(这就是“魔法”发生的地方),在您的函数中,在将圆圈加载到视图中时drawCircles
更新变量。finalHeight
JourneyChlgCircleView(arguments: ...)
.someModifiers()
.onAppear {
if i == points.count - 1 {
finalHeight = points[i].y + circleRadius
}
}
Run Code Online (Sandbox Code Playgroud)
通过在迭代结束时更新最终高度,GeometryReader
将重新绘制自身并以正确的布局呈现其子级。
或者,finalHeight
如果您的实现需要这样的事情,您可以在添加每个圆圈时迭代更新变量。但是,在我看来,这会导致整个视图的过度重绘。
最后结果
请注意,在此结果预览中,GeometryReader 在视图顶部打印其当前高度,并且每个圆圈都与其当前 Y 位置重叠。
归档时间: |
|
查看次数: |
2404 次 |
最近记录: |