SwiftUI VStack 和 Spacer 的行为不符合文档

SwiftUI VStack and Spacer not behaving according to docs

在 SwiftUI Apple Watch 应用中,我们需要在垂直滚动视图中对齐一些文本,以便:

这是我目前的看法:

import SwiftUI

struct ContentView: View {

  var body: some View {
    GeometryReader { geometry in
      ZStack {
        ScrollView(.vertical) {
          VStack(alignment: .leading, spacing: 0) {
            Spacer()
              .frame(minHeight: geometry.size.height / 2)
            Text("Title")
              .bold()
              .background(.red.opacity(0.2))
            Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
              .background(.red.opacity(0.2))
          }
            .frame(minHeight: geometry.size.height - 20)
            .background(.blue.opacity(0.4))
        }
      }
        .padding(10)
        .frame(width: geometry.size.width, height: geometry.size.height)
    }
      .ignoresSafeArea()
  }
}

如您所见,Spacerframe(minHeight: ...) 部分用于将内容放在底部。 ScrollView 的大小固定为整个屏幕。

但是,文本没有完整显示。外观如下:

如您所见,内容并非从屏幕中间开始。显然 Spacer 变得比它的 minHeight 大。但是,Spacer 被记录为仅在 VStack 中占用多余的 space。 VStack 中没有多余的 space,所以 Spacer 应该只和它的 minHeight.

一样高

我错过了什么?

下面是滚动到底部时的样子:

为什么这段文字被剪掉了?

完全不需要spacer,可以通过frame alignment来对齐,考虑到的场景更合适

var body: some View {
    GeometryReader { geometry in
        ZStack {
            ScrollView(.vertical) {
                VStack(alignment: .leading, spacing: 0) {
                    Spacer()
                        .frame(height: geometry.size.height / 2) // << fixed !!
                    Text("Title")
                        .bold()
                        .background(.red.opacity(0.2))
                    Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
                        .background(.red.opacity(0.2))
                }
                .frame(minHeight: geometry.size.height - 20, alignment: .bottom) // << !!
                .background(.blue.opacity(0.4))
            }
        }
        .padding(10)
    }
    .ignoresSafeArea()
}

使用 Xcode 13.2

测试

解决此问题最重要的部分是布局优先级。为了防止 Spacer 无限扩展(因为 ScrollView 会给出它想要的任何 space 内容),我们需要定义优先级。在没有定义优先级的情况下,默认情况下 SwiftUI 似乎只是在 Spacer 和所有 Text 之间均匀分布布局 50/50。

我们可以降低 Spacer 扩展的优先级 layoutPriority(_:) 修饰符。还进行了其他一些小调整,例如为 VStack 设置最小高度,以便文本在较短时与底部对齐。

完整代码:

struct ContentView: View {
    var body: some View {
        GeometryReader { geo in
            ScrollView {
                VStack(alignment: .leading, spacing: 0) {
                    Spacer()
                        .frame(maxWidth: .infinity, minHeight: geo.size.height / 2)
                        .layoutPriority(-1)

                    Text("Title")
                        .bold()
                        .background(.red.opacity(0.2))

                    Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
//                    Text("Much shorter text.")
                        .background(.red.opacity(0.2))
                }
                .frame(minHeight: geo.size.height)
                .background(.blue.opacity(0.4))
            }
        }
        .padding(10)
        .navigationBarHidden(true)
        .ignoresSafeArea()
    }
}

结果:

Long text Short text