按比例将空 space 分成 HStack/VStack
Divide empty space in HStack/VStack proportionally
我有一个带间隔符和文本的 HStack。
HStack {
Spacer()
Text("Some Text")
Spacer()
}
现在我希望第一个 Spacer 占用 X% 的可用空间 space(不包括 Text
占用的 space),底部占用其余部分。
如果我使用 Geometry Reader,并对第一个 Spacer -
执行如下操作
Spacer()
.frame(width: geometry.size.width * (X/100))
没有考虑Text
占用的space。
有什么方法可以在 spacers 之间划分“可用”space?
您要找的是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 = [=10=]
}
.onPreferenceChange(HStackWidthPrefKey.self) {
// This keeps track of the change of the size
hStackWidth = [=10=]
}
}
}
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
是如何工作的。
这是一个不错的解决方案,只需要一个 GeometryReader
。
有关详细信息,请查看 this 文章。
struct MyView: View {
@State private var offset: CGFloat?
var body: some View {
HStack {
Spacer()
Text("Some Text")
.anchorPreference(key: BoundsPreference.self, value: .bounds) { [=10=] }
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[[=10=]].width))
}
}
}
.onPreferenceChange(OffsetPreference.self) {
offset = [=10=]
}
}
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()
}
}
我有一个带间隔符和文本的 HStack。
HStack {
Spacer()
Text("Some Text")
Spacer()
}
现在我希望第一个 Spacer 占用 X% 的可用空间 space(不包括 Text
占用的 space),底部占用其余部分。
如果我使用 Geometry Reader,并对第一个 Spacer -
执行如下操作Spacer()
.frame(width: geometry.size.width * (X/100))
没有考虑Text
占用的space。
有什么方法可以在 spacers 之间划分“可用”space?
您要找的是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 = [=10=]
}
.onPreferenceChange(HStackWidthPrefKey.self) {
// This keeps track of the change of the size
hStackWidth = [=10=]
}
}
}
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
是如何工作的。
这是一个不错的解决方案,只需要一个 GeometryReader
。
有关详细信息,请查看 this 文章。
struct MyView: View {
@State private var offset: CGFloat?
var body: some View {
HStack {
Spacer()
Text("Some Text")
.anchorPreference(key: BoundsPreference.self, value: .bounds) { [=10=] }
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[[=10=]].width))
}
}
}
.onPreferenceChange(OffsetPreference.self) {
offset = [=10=]
}
}
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()
}
}