在SwiftUI中使VStack占据屏幕宽度

268

考虑以下代码:

import SwiftUI

struct ContentView: View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Title")
        .font(.title)

      Text("Content")
        .lineLimit(nil)
        .font(.body)

      Spacer()
    }
    .background(Color.red)
  }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
#endif

这导致产生以下界面:

预览

如何使VStack填充屏幕的宽度,即使标签/文本组件不需要完整的宽度?


我发现的一种技巧是在结构中插入一个空的 HStack,如下所示:

VStack(alignment: .leading) {
  HStack {
    Spacer()
  }
  Text("Title")
    .font(.title)

  Text("Content")
    .lineLimit(nil)
    .font(.body)

  Spacer()
}

这样就能得到所需的设计:

desired output

是否有更好的方法?


1
请查看我的答案,其中展示了最多5种实现类似结果的替代方法。 - Imanou Petit
我添加了一种新的方法来完成这个任务,相比其他答案,它需要更少的代码,并且可重复使用!https://dev59.com/eVMI5IYBdhLWcg3wUp42#67147114 - ios coder
19个回答

381

尝试使用以下选项中的.frame修饰器:

.frame(
      minWidth: 0,
      maxWidth: .infinity,
      minHeight: 0,
      maxHeight: .infinity,
      alignment: .topLeading
    )
struct ContentView: View {
  var body: some View {
    VStack(alignment: .leading) {
      Text("Hello World")
        .font(.title)
      Text("Another")
        .font(.body)
      Spacer()
    }
    .frame(
      minWidth: 0,
      maxWidth: .infinity,
      minHeight: 0,
      maxHeight: .infinity,
      alignment: .topLeading
    )
    .background(Color.red)
  }
}

这被描述为一种灵活的框架(请参见文档),它将伸展以填满整个屏幕,当它有额外的空间时,它将把其内容居中显示。


33
这是错误的。.background(Color.red) 是窗口视图的背景,而不是在窗口视图中的 VStack的背景(原因在 WWDC 视频中有解释 :P)。 如果在 .frame 之前加入 .background(Color.green),你会发现 VStack 仍然具有适合其内容的宽度,而框架具有红色背景... 当你加上 .frame(...) 时,它不是编辑 VStack,而是创建了另一个视图。 - Jake
2
有一个问题:既然我们已经使用了VStack(alignment:.leading),为什么要使用alignment:.topLeading?谢谢分享。 - UIChris
抱歉,您没有提供需要翻译的文本。请提供需要翻译的内容。 - PipEvangelist
实际上,如果你是一个极简主义者,.frame(maxWidth: .infinity, alignment: .topLeading) 对于大多数情况来说已经足够了。 - mojuba
“alignment: .topLeading” 是我所需的参数。谢谢! - Konstantin Nikolskii
显示剩余3条评论

90

在 Swift 5.2 和 iOS 13.4 中,根据您的需求,您可以使用以下示例之一将您的 VStack 对齐到顶部前导约束并具有全尺寸框架。

请注意,下面的代码片段都会产生相同的显示效果,但不能保证 VStack 的有效框架或出现在调试视图层次结构中的 View 元素数量。


1. 使用 frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) 方法

最简单的方法是使用 frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) 方法设置您的 VStack 的最大宽度和高度,并传递所需的对齐方式:

struct ContentView: View {

    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            Text("Content")
                .font(.body)
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity,
            alignment: .topLeading
        )
        .background(Color.red)
    }

}

2. 使用Spacer强制对齐

您可以将VStack嵌入全尺寸的HStack中,并使用末尾和底部的Spacer来强制使VStack顶部对齐:

struct ContentView: View {

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)

                Spacer() // VStack bottom spacer
            }

            Spacer() // HStack trailing spacer
        }
        .frame(
            maxWidth: .infinity,
            maxHeight: .infinity
        )
        .background(Color.red)
    }

}

3. 使用 ZStack 和全尺寸的背景 View

这个例子展示了如何将你的 VStack 嵌套在一个顶部靠左对齐的 ZStack 中。请注意使用 Color 视图设置最大宽度和高度:

struct ContentView: View {

    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.red
                .frame(maxWidth: .infinity, maxHeight: .infinity)

            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
        }
    }

}

4. 使用 GeometryReader

GeometryReader 的定义如下:

一个容器视图,其内容的函数基于它自己的大小和坐标空间。[...]该视图向其父布局返回一个灵活的首选大小。

以下代码片段展示了如何使用 GeometryReader 将你的 VStack 与顶部前导约束和全尺寸框架对齐:

struct ContentView : View {

    var body: some View {
        GeometryReader { geometryProxy in
            VStack(alignment: .leading) {
                Text("Title")
                    .font(.title)
                Text("Content")
                    .font(.body)
            }
            .frame(
                width: geometryProxy.size.width,
                height: geometryProxy.size.height,
                alignment: .topLeading
            )
        }
        .background(Color.red)
    }

}

5. 使用 overlay(_:alignment:) 方法

如果您想要将您的 VStack 与现有全尺寸的 View 顶部前导约束对齐,您可以使用 overlay(_:alignment:) 方法:

struct ContentView: View {

    var body: some View {
        Color.red
            .frame(
                maxWidth: .infinity,
                maxHeight: .infinity
            )
            .overlay(
                VStack(alignment: .leading) {
                    Text("Title")
                        .font(.title)
                    Text("Content")
                        .font(.body)
                },
                alignment: .topLeading
            )
    }

}

显示:


4
这应该被接受为答案。 - lkahtz
非常完整的答案!谢谢! - Spencer Müller Diniz

67

以下是一种可行且可能更加直观的备选堆叠方式:

struct ContentView: View {
    var body: some View {
        HStack() {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
            }
            Spacer()
        }.background(Color.red)
    }
}

如果需要,也可以通过删除 Spacer() 来轻松重新定位内容。

添加/删除 Spacers 以更改位置


8
好的替代品。喜欢这个gif。 - ielyamani
1
我认为这种方法更直观,也更符合OP的问题,我理解OP的问题是如何使Stack元素填充其容器,而不是如何在元素周围插入容器。 - brotskydotcom
1
我认为这是与SwiftUI背后的理念最符合的最佳答案。 - krummens
1
这绝对是在这里针对如此常见的布局最符合SwiftUI风格的答案。 - dead_can_dance

35

有更好的方法!

要让VStack填充其父视图的宽度,可以使用GeometryReader并设置框架(.relativeWidth(1.0)应该有效,但目前似乎不起作用)。

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("test")
            }
                .frame(width: geometry.size.width,
                       height: nil,
                       alignment: .topLeading)
        }
    }
}
为了使VStack的宽度与实际屏幕宽度相同,您可以在设置框架时使用UIScreen.main.bounds.width而不是使用GeometryReader。但我想您可能希望获取父视图的宽度。

此外,这种方法还具有另一个好处,即不会在VStack中添加间距。如果您使用Spacer()作为HStack内容添加到VStack中,则可能会发生这种情况(如果您有间距)。

更新-没有更好的方法!

查看已接受的答案后,我意识到该答案实际上并不起作用!乍一看似乎有效,但如果您将VStack更新为具有绿色背景,您会注意到VStack仍然具有相同的宽度。

struct ContentView : View {
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                Text("Hello World")
                    .font(.title)
                Text("Another")
                    .font(.body)
                Spacer()
                }
                .background(Color.green)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
                .background(Color.red)
        }
    }
}
这是因为.frame(...)实际上会向视图层次结构中添加另一个视图,并且该视图最终会填充屏幕。然而,VStack仍然没有填满。

这个问题在我的回答中似乎也是一样的,可以使用与上述相同的方法进行检查(在.frame(...)之前和之后放置不同的背景颜色)。似乎唯一真正扩大VStack的方法是使用间隔器:

struct ContentView : View {
    var body: some View {
        VStack(alignment: .leading) {
            HStack{
                Text("Title")
                    .font(.title)
                Spacer()
            }
            Text("Content")
                .lineLimit(nil)
                .font(.body)
            Spacer()
        }
            .background(Color.green)
    }
}

尝试过使用relativeWidth,但没有成功。不知何故,如果在.background(Color.red)之前放置.frame,则GeometryReader可以正常工作。 - ielyamani
当我在“显示 SwiftUI 检查器”后“添加修饰符”,或者在 UI 的任何其他地方中,都找不到 GeometryReader。你是如何发现 GeometryReader 的? - gone
1
@已删除 - Stack Overflow :) - Jake
经过测试提出的解决方案(也使用了iOS 15 beta),结果发现,实现一个_正确_的解决方案的唯一方法是使用GeometryReader。部分原因是,使用“frame”修饰符用.infinity值填充整个屏幕实际上并不总是你想要的结果。 - CouchDeveloper

21

编辑:使用 .frame 的简单(更好的)方法已更新答案。

只需使用帧修改器!

struct Expand: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Title")
                .font(.title)
            
            Text("Content")
                .lineLimit(nil)
                .font(.body)
        }
        .frame(maxWidth:.infinity,maxHeight:.infinity,alignment:.topLeading)
        .background(Color.red)
    }
}

注意 - 在VStack中您甚至不需要使用spacer!

注意2 - 如果您不想要顶部和底部的白色填充,则可以在背景中使用:

Color.red.edgesIgnoringSafeArea(.all)

enter image description here


这不是全屏。 - Duck
2
问题并没有要求全屏,而是要求全宽。如果您想扩展到安全区域之外,请使用.edgesIgnoringSafeArea。 - Confused Vorlon

14

我处理这个问题最简单的方法是使用ZStack + .edgesIgnoringSafeArea(.all)

struct TestView : View {

    var body: some View {
        ZStack() {
            Color.yellow.edgesIgnoringSafeArea(.all)
            VStack {
                Text("Hello World")
            }
        }
    }
}

11

预览图片

方法一 -> 使用MaxWidth和MaxHeight

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

方法2->使用主屏幕边界

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
        }
        .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height)
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

第三种方法 -> 使用Geometry Reader

import SwiftUI

struct SomeView: View {
    var body: some View {
        GeometryReader { geometryReader in
            VStack {
                Text("Hello, World!")
            }
            .frame(maxWidth: geometryReader.size.width, maxHeight: geometryReader.size.height)
            .background(.red)
        }
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

第四种方法 -> 使用间隔块

import SwiftUI

struct SomeView: View {
    var body: some View {
        VStack {
            Text("Hello, World!")
            HStack{
                Spacer()
            }
            Spacer()
        }
        .background(.red)
    }
}

struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        SomeView()
    }
}

6

使用这个

.edgesIgnoringSafeArea(.all)

是的!谢谢!就是这样! - Jszn

6
一个好的解决方案,而且没有“奇技淫巧”,就是被遗忘的ZStack
ZStack(alignment: .top){
    Color.red
    VStack{
        Text("Hello World").font(.title)
        Text("Another").font(.body)
    }
}

结果:

在此输入图片描述


5
你可以使用 GeometryReader 来实现。 GeometryReader

代码:

struct ContentView : View {
    var body: some View {
        GeometryReader { geometry in
            VStack {
               Text("Turtle Rock").frame(width: geometry.size.width, height: geometry.size.height, alignment: .topLeading).background(Color.red)
            }
        }
    }
}

你的输出结果类似于:

输入图像描述


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