SwiftUI - 相对于其锚点定位覆盖层

Air*_*les 3 xcode swift swiftui

我有一个包含 2 个视图的 ZStack:

  • ReferenceContent - 有一些内容和一个分隔符。这是屏幕上的主要内容

  • popoverContent - 是一个条件弹出窗口,仅占据屏幕的一小部分。

var body: some View {
  ZStack {
    referenceContent

    if popoverCondition {
      popoverContent
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我希望popoverContent的顶部边缘与referenceContent的底部对齐

有人知道如何实现这一点吗?或者有没有比我现在更好的方式来查看这个弹出窗口?谢谢!

Dam*_*aux 9

您可以使用overlay(alignment:content:)修改器(以前overlay(_:alignment:))结合自定义对齐参考线来完成此操作。

基本思想是将参考视图的底部弹出视图的顶部对齐。

烦人的是,覆盖修改器只允许您指定一个对齐参考线(对于两个视图)。因此,如果您编写,stack1.overlay(alignment: .bottom) { stack2 }它会将参考文献的底部与覆盖层的底部对齐。解决这个问题的一个快速方法是覆盖覆盖层的底部对齐指南并返回顶部。

referenceView
  .overlay(alignment: .bottom) {
    popoverContent
      // overwrites bottom alignment of the popover with its top alignment guide.
      .alignmentGuide(.bottom) {$0[.top]}
  }
Run Code Online (Sandbox Code Playgroud)

Overlay 与 ZStack

您可能会问:“为什么不使用 ZStack 而不是覆盖层?”。两者之间的区别在于,ZStack 在布局整个视图(参考 + 弹出窗口)时会考虑弹出窗口的大小。这与弹出窗口应该做的相反。对于弹出窗口,布局系统应该只考虑参考视图的大小,并在其顶部绘制弹出窗口(不影响参考的布局)。这正是overlay(...)修改器的作用。

旧 API(iOS 15、macOS 12 之前)

在旧版本的 SwiftUI 中,overlay 修饰符的参数顺序相反。因此,这些旧系统的代码示例是:

referenceView
  .overlay(
    popoverContent.alignmentGuide(.bottom) {$0[.top]},
    alignment: .bottom
  )
Run Code Online (Sandbox Code Playgroud)

自定义对齐指南

当您不想覆盖现有对齐参考线(例如,因为您在其他地方需要它)时,您也可以使用自定义对齐参考线。这是一个更通用的示例,使用名为的自定义对齐指南Alignment.TwoSided

extension View {
    @available(iOS 15.0, *)
    func overlay<Target: View>(align originAlignment: Alignment, to targetAlignment: Alignment, of target: Target) -> some View {
        let hGuide = HorizontalAlignment(Alignment.TwoSided.self)
        let vGuide = VerticalAlignment(Alignment.TwoSided.self)
        return alignmentGuide(hGuide) {$0[originAlignment.horizontal]}
            .alignmentGuide(vGuide) {$0[originAlignment.vertical]}
            .overlay(alignment: Alignment(horizontal: hGuide, vertical: vGuide)) {
                target
                    .alignmentGuide(hGuide) {$0[targetAlignment.horizontal]}
                    .alignmentGuide(vGuide) {$0[targetAlignment.vertical]}
            }
    }
}

extension Alignment {
    enum TwoSided: AlignmentID {
        static func defaultValue(in context: ViewDimensions) -> CGFloat { 0 }
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

referenceView
  .overlay(align: .bottom, to: .top, of: popoverContent)
Run Code Online (Sandbox Code Playgroud)