我正在尝试创建一个视图,其中 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)
}
}
上述实现对于宽度非常有效(见下文):
这样做的问题是父级不知道它需要多高,因此圆形视图的顶部和底部超出了父级的边界。
一旦我在 CircleView 中设置框架的高度,圆形视图就会缩小到 16 点。
我已经调试了视图的大小,通过上面的内容,当仅提供宽度时,
maxSize
返回为(110.666,110.666),但是当提供高度时,大小返回为(16.0,16.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)
}
}
}