与SwiftUI相等的子视图宽度

goh*_*tis 9 watchos swiftui

我正在尝试使用SwiftUI构建一个简单的watchOS UI,并在按钮上方并排放置两条信息。

我想每一侧(表示为VStackHStack)占用一半的可用宽度的(因此它是黄色的父视图内的偶数50/50)划分,其中| 在下面的示例中,字符位于按钮的中心。

我想要短期长期!!!文字以每边50%为中心。

我从以下代码开始,以使元素就位并显示一些不同堆栈的边界:

var body: some View {
    VStack {
        HStack {

            VStack {
                Text("Short").font(.body)
            }
                .background(Color.green)

            VStack {
                Text("Longer!!!").font(.body)
            }
                .background(Color.blue)

        }
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.yellow)
        Button (action: doSomething) {
            Text("|")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这给了我这个结果: 初始点

然后,当将每个并排VStack宽度设置为可用宽度的50%时,我陷入了困境。我认为将其添加.relativeWidth(0.5)到每个对象中应该是可行的VStack,据我所知,应该将每个对象VStack的宽度都设为其父视图的宽度(HStack带有黄色背景的):

var body: some View {
    VStack {
        HStack {

            VStack {
                Text("Short").font(.body)
            }
                .relativeWidth(0.5)
                .background(Color.green)

            VStack {
                Text("Longer!!!").font(.body)
            }
                .relativeWidth(0.5)
                .background(Color.blue)

        }
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.yellow)
        Button (action: doSomething) {
            Text("|")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但这是我得到的结果: 意外的宽度

如何使用SwiftUI获得想要的行为?


更新:在进一步查看了SwiftUI文档之后,我在这里看到了一个示例该示例设置了一个框架,然后定义了与该框架相比的相对宽度,所以也许我不应该以relativeWidth这种方式使用它?

通过以下代码,我离想要的东西更近了:

var body: some View {
    VStack {
        HStack {

            VStack {
                Text("Short").font(.body)
            }
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(Color.green)

            VStack {
                Text("Longer!!!").font(.body)
            }
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(Color.blue)

        }
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.yellow)
        Button (action: doSomething) {
            Text("|")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

产生以下结果:

接近预期结果

现在,我试图找出是什么原因在两个VStacks 之间的中间创建了额外的空间。到目前为止,尝试摆脱填充并忽略安全区域的尝试似乎并未对其产生影响。

goh*_*tis 10

我仍然对relativeWidth应该何时使用以及如何使用感到困惑,但是我无需使用它就能实现我想要的愿望。(编辑2019年7月18日:根据iOS 13 Beta 4发行说明,relativeWidth现已弃用)

在我的问题的最新更新中,我在两侧之间有一些额外的间距,并且意识到这是默认间距,HStack并且我能够通过将其间距设置为0来删除该间距。这是最终的代码和结果:

var body: some View {
    VStack {
        HStack(spacing: 0) {

            VStack {
                Text("Short").font(.body)
            }
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(Color.green)

            VStack {
                Text("Longer!!!").font(.body)
            }
                .frame(minWidth: 0, maxWidth: .infinity)
                .background(Color.blue)

        }
            .frame(minWidth: 0, maxWidth: .infinity)
            .background(Color.yellow)
        Button (action: doSomething) {
            Text("|")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

结果如下: 没有间隔的最终结果


Hex*_*ons 9

以下是如何为watchOS 9、iOS 16、tvOS 16 和 macOS 13创建EqualWidthHStack

等宽文本

这是用法:

struct ContentView: View {
    
    private let strings = ["Hello,", "very very very big", "world!"]

    var body: some View {

        EqualWidthHStack {

            ForEach(strings, id: \.self) { string in

                ZStack {

                    RoundedRectangle(cornerRadius: 10, style: .continuous)
                        .opacity(0.2)

                    Text(string)
                        .padding(10)
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

首先创建一个struct符合Layout.

struct EqualWidthHStack: Layout {
    ...
}
Run Code Online (Sandbox Code Playgroud)

它将带有两个默认方法,以下是实现它们的方法。

适合的尺寸:

func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
    
    let maxSize = maxSize(subviews: subviews)
    let spacing = spacing(subviews: subviews)
    let totalSpacing = spacing.reduce(0.0, +)
    
    return CGSize(width: maxSize.width * CGFloat(subviews.count) + totalSpacing,
                  height: maxSize.height)
}
Run Code Online (Sandbox Code Playgroud)

放置子视图:

func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
    
    let maxSize = maxSize(subviews: subviews)
    let spacing = spacing(subviews: subviews)
    
    let sizeProposal = ProposedViewSize(width: maxSize.width,
                                        height: maxSize.height)
    
    var x = bounds.minX + maxSize.width / 2
    
    for index in subviews.indices {
        subviews[index].place(at: CGPoint(x: x, y: bounds.midY),
                              anchor: .center,
                              proposal: sizeProposal)
        x += maxSize.width + spacing[index]
    }
}
Run Code Online (Sandbox Code Playgroud)

您将需要以下两个辅助方法。

最大尺寸:

private func maxSize(subviews: Subviews) -> CGSize {

    let subviewSizes = subviews.map { $0.sizeThatFits(.unspecified) }

    let maxSize: CGSize = subviewSizes.reduce(.zero, { result, size in
        CGSize(width: max(result.width, size.width),
               height: max(result.height, size.height))
    })

    return maxSize
}
Run Code Online (Sandbox Code Playgroud)

间距:

private func spacing(subviews: Subviews) -> [CGFloat] {

    subviews.indices.map { index in

        guard index < subviews.count - 1 else { return 0.0 }

        return subviews[index].spacing.distance(to: subviews[index + 1].spacing, 
                                                along: .horizontal)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是苹果 WWDC22 的视频,介绍如何制作它:

使用 SwiftUI 编写自定义布局