在堆栈视图 SwiftUI 中同等大小的子视图

问题描述 投票:0回答:1

我正在尝试创建一个视图,其中 HStack 中有带有圆形背景的文本视图,其中每个子项的大小根据堆栈中最大的子项进行调整。目标是让所有尺寸类别的所有视图都具有相同的宽度和高度。

我的实现已经完成了 90%,但是在尝试设置视图的宽度和高度时,我遇到了一些非常奇怪的行为。

值得注意的是,由于此视图需要具有固定宽度,因此我无法使用

.infinity
作为宽度。此外,我需要以此为目标 iOS 13,因此我无法使用任何涉及 iOS 14+ 的较新 API 的解决方案。

这是我的代码:

import SwiftUI

// MARK: - Testview

struct Testview: View {
    // MARK: Internal

    var body: some View {
        HStack {
            CircleView(number: "2", maxSize: maxSize)
            CircleView(number: "24", maxSize: maxSize)
            CircleView(number: "246", maxSize: maxSize)
        }
        .background(Color.orange)
        .onPreferenceChange(CircleSizePreferenceKey.self, perform: { value in
            maxSize = value
        })
    }

    // MARK: Private

    @State private var maxSize: CGSize = .zero
}

#Preview {
    Testview()
        .environment(\.sizeCategory, .extraLarge)
}

// MARK: - CircleView

struct CircleView: View {
    // MARK: Lifecycle

    init(number: String?, maxSize: CGSize) {
        if
            let number,
            !number.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            self.number = number
        } else {
            self.number = " " // To preserve the size since an empty string results in a default sized circle (16.0 pts)
        }

        self.maxSize = maxSize
    }

    // MARK: Internal

    var number: String
    var maxSize: CGSize

    var body: some View {
        Text(number)
            .padding(8.0)
            .background(
                GeometryReader { geometry in
                    Color.clear
                        .preference(
                            key: CircleSizePreferenceKey.self,
                            value: geometry.size
                        )
                }
            )
            .frame(width: maxSize.width)
            .background(
                Circle()
                    .fill(Color.white)
                    .frame(width: maxSize.width, height: maxSize.height)
                    .background(
                        Circle()
                            .stroke(.black.opacity(0.15), lineWidth: 1.5)
                    )
            )
    }
}

// MARK: - CircleSizePreferenceKey

private struct CircleSizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero

    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        let newSize = nextValue()
        let maxSize = max(value.width, value.height, newSize.width, newSize.height)

        value = CGSize(width: maxSize, height: maxSize)
    }
}

上述实现对于宽度非常有效(见下文):

width only

这样做的问题是父级不知道它需要多高,因此圆形视图的顶部和底部超出了父级的边界。

一旦我在 CircleView 中设置框架的高度,圆形视图就会缩小到 16 点。

width and height

我已经调试了视图的大小,通过上面的内容,当仅提供宽度时,

maxSize
返回为(110.666,110.666),但是当提供高度时,大小返回为(16.0,16.0) ).

有人可以帮忙解决这个问题吗?如果我在这里做错了什么,或者是否有更好的方法来实现我想要实现的目标,请告诉我?

ios swiftui geometry geometryreader hstack
1个回答
0
投票

我建议使用隐藏的占位符来定义

CircleView
的足迹。然后将
CircleView
显示为覆盖在足迹上。

  • 足迹的宽度可以通过使用
    ZStack
    将所有数字相互叠加来构建。
    ZStack
    采用最大数的维度。
  • 为了使足迹的高度与宽度相同(换句话说,形成正方形),在
    GeometryReader
    的背景中使用了
    ZStack
    。由
    GeometryReader
    测量的宽度将作为
    minHeight
    应用于
    ZStack
  • 叠加层的框架与其应用到的视图相同。如果您在此框架中显示一个圆圈,它将填充整个框架。
  • 数字本身可以显示为覆盖在圆圈上。

此方法适用于 iOS 13(及更高版本),并且不需要

PreferenceKey

struct Testview: View {
    private let numbers = ["2", "24", "246"]
    @State private var maxSize: CGFloat = .zero

    private var footprint: some View {
        ZStack {
            ForEach(Array(numbers.enumerated()), id: \.offset) { offset, numberString in
                Text(numberString)
            }
        }
        .padding(8.0)
        .background {
            GeometryReader { proxy in
                Color(white: 1, opacity: 0)
                    .onAppear { maxSize = proxy.size.width }
            }
        }
        .frame(minHeight: maxSize)
        .hidden()
    }

    var body: some View {
        HStack {
            ForEach(Array(numbers.enumerated()), id: \.offset) { offset, numberString in
                footprint
                    .overlay {
                        CircleView(number: numberString)
                    }
            }
        }
        .background(Color.orange)
    }
}

struct CircleView: View {
    let number: String

    init(number: String?) {
        if let number,
           !number.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            self.number = number
        } else {
            self.number = " " // To preserve the size since an empty string results in a default sized circle (16.0 pts)
        }
    }

    var body: some View {
        Circle()
            .fill(Color.white)
            .stroke(.black.opacity(0.15), lineWidth: 1.5)
            .overlay {
                Text(number)
            }
    }
}

Screenshot

© www.soinside.com 2019 - 2024. All rights reserved.