0

我有一个带间隔和文本的 HStack。

HStack {
    Spacer()
    Text("Some Text")
    Spacer()
}

现在我希望第一个 Spacer 占用 X% 的可用空间(不包括 占用的空间Text),而底部占用其余空间。

如果我使用 Geometry Reader,并对第一个 Spacer 执行以下操作 -

Spacer()
    .frame(width: geometry.size.width * (X/100))

它没有考虑Text.

有没有办法在垫片之间划分“可用”空间?

4

2 回答 2

1

您正在寻找的是PreferenceKeys. 本质上,它们会跟踪某些视图的大小,然后您可以使用这些大小来计算您需要的内容。它们最常用于使多个视图保持相同大小,即使它们具有不同的大小。我给你一个长短的解决方案:

长解决方案:

struct SpacerPrefKeyView: View {
    
    @State private var textWidth: CGFloat = 10
    @State private var hStackWidth: CGFloat = 10
    
    let X: CGFloat = 20

    var body: some View {
        HStack {
            Spacer()
                .frame(width: (hStackWidth - textWidth) * (X/100))
            Text("Hello, World!")
                .background(GeometryReader { geometry in
                    Color.clear.preference(
                        //This sets the preference key value with the width of the background view
                        key: TextWidthPrefKey.self,
                        value: geometry.size.width)
                })

            Spacer()
        }
        .background(GeometryReader { geometry in
            Color.clear.preference(
                //This sets the preference key value with the width of the background view
                key: HStackWidthPrefKey.self,
                value: geometry.size.width)
        })
        .onPreferenceChange(TextWidthPrefKey.self) {
            // This keeps track of the change of the size
            textWidth = $0
        }
        .onPreferenceChange(HStackWidthPrefKey.self) {
            // This keeps track of the change of the size
            hStackWidth = $0
       }
    }
}

private extension SpacerPrefKeyView {
    struct TextWidthPrefKey: PreferenceKey {
        static let defaultValue: CGFloat = 0
        
        static func reduce(value: inout CGFloat,
                           nextValue: () -> CGFloat) {
            value = max(value, nextValue())
        }
    }
    
    struct HStackWidthPrefKey: PreferenceKey {
        static let defaultValue: CGFloat = 0
        
        static func reduce(value: inout CGFloat,
                           nextValue: () -> CGFloat) {
            value = max(value, nextValue())
        }
    }
}

感谢 FiveStarBlog 和 Twitter 上的@ramzesenok 的简短解决方案:

struct SpacerPrefKeyView: View {
    
    @State private var textSize: CGSize = CGSize(width: 10, height: 10)
    @State private var hStackSize: CGSize = CGSize(width: 10, height: 10)
    
    let X: CGFloat = 20

    var body: some View {
        HStack {
            Spacer()
                .frame(width: (hStackSize.width - textSize.width) * (X/100))
            Text("Hello, World!")
                .copySize(to: $textSize)
            Spacer()
        }
        .copySize(to: $hStackSize)
    }
}

并使用扩展名:

extension View {
    func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
        background(
            GeometryReader { geometryProxy in
                Color.clear
                    .preference(key: SizePreferenceKey.self, value: geometryProxy.size)
            }
        )
            .onPreferenceChange(SizePreferenceKey.self, perform: onChange)
    }
    
    func copySize(to binding: Binding<CGSize>) -> some View {
        self.readSize { size in
            binding.wrappedValue = size
        }
    }
}

他们做同样的事情,但是扩展非常巧妙地处理它,而您的视图中没有额外的代码。我把它贴出来让你看看它是如何PreferenceKeys工作的。

于 2022-01-16T17:58:59.463 回答
1

这是一个体面的解决方案,只需要一个GeometryReader.

有关更多信息,请查看这篇文章。

struct MyView: View {
    @State private var offset: CGFloat?

    var body: some View {
        HStack {
            Spacer()
            Text("Some Text")
                .anchorPreference(key: BoundsPreference.self, value: .bounds) { $0 }
            Spacer(minLength: offset)
        }
        .backgroundPreferenceValue(BoundsPreference.self) { preferences in
            GeometryReader { g in
                preferences.map {
                    Color.clear.preference(key: OffsetPreference.self, value: offset(with: g.size.width - g[$0].width))
                }
            }
        }
        .onPreferenceChange(OffsetPreference.self) {
            offset = $0
        }
    }

    private func offset(with widthRemainder: CGFloat) -> CGFloat {
        widthRemainder * (100.0 - X) / 100.0
    }
}

private struct BoundsPreference: PreferenceKey {
    typealias Value = Anchor<CGRect>?
    static var defaultValue: Value = nil
    
    static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
        value = nextValue()
    }
}

private struct OffsetPreference: PreferenceKey {
    typealias Value = CGFloat?
    static var defaultValue: Value = nil
    
    static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
        value = nextValue()
    }
}
于 2022-01-16T18:00:34.467 回答