3

この質問に基づいています: 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.点に円を配置するだけです)

問題:これで、親と子から高さを取得する必要が
あることがわかりました。これは「鶏卵問題」になります。だから私は手動での高さを計算したい。GeometryReaderScrollViewGeometryReader


私の試み

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
                    }
        }
    }

  }
}
4

2 に答える 2