この質問に基づいています: SwiftUI - ビュー間の描画 (曲線) パス
を使用して、必要なビューとパスを描画しますGeometryReader
。問題は、画面が長くなりすぎてすべてを表示できないことです (そのようなビューは 15 ありますが、ここでは 6 つしか表示されません)。
ScrollView
だから...私は全体にaを追加します。そう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...")
}
}
(内容は基本的に1.画面サイズから点を計算する、2.点に沿ってパスを描く、3.点に円を配置するだけです)
問題:これで、親と子から高さを取得する必要が
あることがわかりました。これは「鶏卵問題」になります。だから私は手動での高さを計算したい。GeometryReader
ScrollView
GeometryReader
私の試み
1 回目の試行
最も簡単な方法は、いくつかの固定値を取得して、大まかな値を計算することです。これをに追加するだけ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))
これは機能します。しかし、今は非常に長い画面があり、下部に不要なスペースがあります。また、電話のモデルによっては、まったく異なるように見える場合があります。
2回目の試行
考えられる解決策の 1 つは、DispatchQueue.main.async
内部を使用して、そこにある計算された子の高さに変数を.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...")
}
}
これはまったく機能しません。はScrollView
何らかの理由で 100 または 10 のいずれかの固定値を受け取り、変更されることはありません。そのブロックを子のいずれかに配置して.background(...)
、それぞれの値を合計しようとしましたが、無限ループしか作成されませんでした。でも、どうにかうまくいく可能性はあったようです。
3回目の試行
このチュートリアルまたはこの回答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
})
と一緒.onPreferenceChange(...)
に。
しかし残念ながら、これはまったく変更されず、最初に 10 のベース値で一度しか呼び出されなかったため、すべての試みの中で最も効果がありませんでした。すべての子ビューに配置しようとしましたが、奇妙な結果しか得られなかったため、それ。
助けが必要
私は今、他の2つの頭痛の種を免れるために、愚かだが機能する最初の試みの解決策を採用したいと思っています。
試み 2 または 3 で私が何を間違えたのか、そしてなぜ私が望む結果を得ることができなかったのかを誰かが理解できますか? 彼らはうまくいくように見えるからです。
完全なコード
リクエストに応じて、ここに完全なクラスがあります (1 回目の試行手法を使用):
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
}
}
}
}
}