SwiftUI:如何强制将HStack中的特定视图置于中心位置

11

假设有以下 HStack:

        HStack{
            Text("View1")

            Text("Centre")

            Text("View2")

            Text("View3")
        }

如何强制“中心”视图在中心位置?

4个回答

19

这里是可能的简单方法。已在Xcode 11.4 / iOS 13.4下测试通过

demo

struct DemoHStackOneInCenter: View {
    var body: some View {
        HStack{
            Spacer().overlay(Text("View1"))

            Text("Centre")

            Spacer().overlay(
                HStack {
                    Text("View2")
                    Text("View3")
                }
            )
        }
    }
}

该解决方案增加了对左/右侧视图的对齐方式,详见相对于另一个居中视图定位视图

的回答。


2
这是一个有趣的方法。它确实会破坏视图之间的间距,但您可以通过在左/右HStacks中添加更多的间隔符和显式间距来解决这个问题。 - Confused Vorlon

8

这个答案需要几个步骤:

  1. 将HStack包裹在VStack中。VStack控制其子元素的水平对齐方式。
  2. 为VStack应用自定义对齐指南。
  3. 创建VStack的子视图,使其占满整个宽度。将自定义对齐指南固定到此视图的中心。(这将自定义对齐指南固定到VStack的中心)
  4. 将“Centre”视图的中心与对齐指南对齐。

对于必须填充VStack的视图,我使用了Geometry Reader。这会自动扩展以占据父视图的大小,而不会影响布局。

import SwiftUI


//Custom Alignment Guide
extension HorizontalAlignment {
    enum SubCenter: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            d[HorizontalAlignment.center]
        }
    }
    
    static let subCentre = HorizontalAlignment(SubCenter.self)
}

struct CentreSubviewOfHStack: View {
    var body: some View {
        //VStack Alignment set to the custom alignment
        VStack(alignment: .subCentre) {
            HStack{
                Text("View1")
                
                //Centre view aligned
                Text("Centre")
                .alignmentGuide(.subCentre) { d in d.width/2 }
                
                Text("View2")
                
                Text("View3")
            }
            
            //Geometry reader automatically fills the parent
            //this is aligned with the custom guide
            GeometryReader { geometry in
                EmptyView()
            }
            .alignmentGuide(.subCentre) { d in d.width/2 }
        }
    }
}

struct CentreSubviewOfHStack_Previews: PreviewProvider {
    static var previews: some View {
        CentreSubviewOfHStack()
            .previewLayout(CGSize.init(x: 250, y: 100))
    }
}

注意:本回答假设您可以设置包含 VStack 的固定高度和宽度。这将防止 GeometryReader 过度“推出”。

在另一种情况下,我用一个矩形替换了 GeometryReader:

            //rectangle fills the width, then provides a centre for things to align to
            Rectangle()
                .frame(height:0)
                .frame(idealWidth:.infinity)
                .alignmentGuide(.colonCentre) { d in d.width/2 }

注意 - 这仍然会展开到最大宽度,除非受到限制!

中心在中央


这对我没有起作用,尽管我可能做错了什么。空视图最终被翻译到屏幕外以保持其与中心文本视图的对齐。一旦我将中心文本视图包装在ZStack中,我就实现了所需的行为。使用EmptyView来对齐某些内容是聪明的,我认为! - pakobongbong

2
"Asperis的答案已经很有趣了,激发了我采用以下方法:"
"不要使用带有叠加层的间隔符,而是可以使用容器,将它们设置为无限宽度,左右紧挨着要居中的元素,以拉伸它们,就像使用间隔符一样。"
HStack {
    // Fills the left side
    VStack {
      Rectangle()
        .foregroundColor(Color.red)
        .frame(width: 120, height: 200)
    }.frame(maxWidth: .infinity)
    
    // Centered
    Rectangle()
      .foregroundColor(Color.red)
      .frame(width: 50, height: 150)
    
    // Fills the right side
    VStack {
      HStack {
        Rectangle()
          .foregroundColor(Color.red)
          .frame(width: 25, height: 100)
        Rectangle()
          .foregroundColor(Color.red)
          .frame(width: 25, height: 100)
      }
    }.frame(maxWidth: .infinity)
  }.border(Color.green, width: 3)

我把它放在了一个ZStack中,以演示居中的文本重叠。

enter image description here

使用容器的优点在于,如果左/右部分比中心部分更高(如截图所示),高度也会传递到父级以使其自适应大小。

1
Asperi的方法让我留下了高度为0的叠加层(这将使内容向上移动,半隐藏在框架上方),我一直在努力修复它,但是很难不完全破坏间距。最终,这成为了一个更简单的解决方案。这也很好,因为它并没有真正违反SwiftUI或利用错误或不可靠的实现细节,而是在HStack中指定具有大小约束的元素。 - Austin

0
我的回答扩展了被接受的答案的想法(并考虑了Confused Vorlon的提示),使HStack在可用空间的整个宽度上展开,并将其中心元素固定在中心位置。
HStack(spacing: .zero) {
  Spacer()
    .overlay {
      HStack(spacing: .zero) {
        Text("View 1")
        Spacer()
      }
    }
  Text("Centre")
  Spacer()
    .overlay {
      HStack(spacing: .zero) {
        Spacer()
        Text("View 2")
      }
    }
}

这个解决方案对我非常有帮助,比如在实现自定义分页界面时。

Broken pagination UI

没有这个技巧,它看起来是这样的:

Correct pagination UI


这对三个视图非常有效。你只需确保左右两侧在尺寸上同等重要即可。 你也可以用更简单的方法达到相同效果。对于两个TextView,使用Text("你的文本").frame(maxWidth:.infinity,alignment:.leading/.trailing)即可。 完全不需要间隔或叠加。 - undefined
没错,但我认为那些“View 1”/“View 2”/“Centre”只是原问题中更复杂视图的占位符,所以我提出了这种更通用的方法。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接