【问题标题】:Make a VStack fill the width of the screen in SwiftUI在 SwiftUI 中使 VStack 填充屏幕的宽度
【发布时间】:2019-10-22 13:09:15
【问题描述】:

鉴于此代码:

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 填满屏幕宽度?


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

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

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

  Spacer()
}

这会产生所需的设计:

有没有更好的办法?

【问题讨论】:

标签: ios swift xcode swiftui


【解决方案1】:

尝试使用带有以下选项的.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)
  }
}

这被描述为一个灵活的框架 (see the documentation),它会伸展以填满整个屏幕,当它有额外的空间时,它会将其内容居中。

【讨论】:

  • 子视图不左对齐 (.leading)。如果放置在.background(Color.red) 之前,则采用全宽
  • 添加了左对齐的代码。现在应该完全匹配
  • 这是错误的。 .backgound(Color.red) 是框架视图的背景,但不是框架视图中VStack 的背景。 (原因在 WWDC 视频中进行了解释:P)。如果您将.background(Color.green) 放在.frame 之前,您将看到VStack 仍然具有适合其内容的宽度,而框架具有红色背景...当您放置.frame(...) 时,它不是在编辑VStack ,它实际上是在创建另一个视图。
  • @Jake 似乎是一场灾难
  • 有一个问题:为什么要使用对齐:.topLeading,因为我们有 VStack(alignment:.leading)?感谢分享
【解决方案2】:

还有更好的办法!

要使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()HStackSpacer() 的内容,则可能会发生这种情况(如果您有间距)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(...) 之前和之后放置不同的背景颜色。似乎实际上扩大了@987654338 的唯一方法@ 是使用分隔符:

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,没用。如果 .frame 放在 .background(Color.red) 之前,GeometryReader 会以某种方式工作
  • 当我在 Show SwiftUI Inspector 之后“添加修饰符”时显示的列表中看不到 GeometryReader,或者在 UI 中的任何其他位置都看不到 GeometryReader。你是怎么发现GeometryReader的?
  • @gone - 堆栈内存溢出:)
  • 在测试了建议的解决方案(也适用于 iOS 15 测试版)后,结果证明,完成正确解决方案的唯一方法是使用 GeometryReader。部分原因是因为用 frame 修饰符填充整个屏幕的 .infinity 值实际上并不总是你想要的。
【解决方案3】:

你可以通过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)
            }
        }
    }
}

您的输出如下:

【讨论】:

    【解决方案4】:

    另一种选择是将其中一个子视图放在HStack 内,并在其后放置Spacer()

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

    导致:

    【讨论】:

    • 恕我直言,这是最优雅的解决方案
    【解决方案5】:

    另一种可行且可能更直观的堆叠安排如下:

    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() 轻松重新定位内容。

    【讨论】:

    • 不错的选择??喜欢这张gif
    • 我发现这更直观,也更符合 OP 的问题,我认为这是关于如何让 Stack 元素填充其容器,而不是如何在元素周围插入容器。
    • 我发现这是符合 SwiftUI 背后理念的最佳答案
    • 对于这种常见的布局,这里绝对是最适合 SwiftUI 的答案。
    【解决方案6】:

    这对我有用(ScrollView(可选)因此可以根据需要添加更多内容,以及居中的内容):

    import SwiftUI
    
    struct SomeView: View {
        var body: some View {
            GeometryReader { geometry in
                ScrollView(Axis.Set.horizontal) {
                    HStack(alignment: .center) {
                        ForEach(0..<8) { _ in
                            Text("?")
                        }
                    }.frame(width: geometry.size.width, height: 50)
                }
            }
        }
    }
    
    // MARK: - Preview
    
    #if DEBUG
    struct SomeView_Previews: PreviewProvider {
        static var previews: some View {
            SomeView()
        }
    }
    #endif
    

    结果

    【讨论】:

      【解决方案7】:

      使用这个

      .edgesIgnoringSafeArea(.all)
      

      【讨论】:

      • 是的!谢谢!就是这样!
      【解决方案8】:

      我设法解决问题的最简单方法是使用 ZStack + .edgesIgnoringSafeArea(.all)

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

      【讨论】:

        【解决方案9】:

        我知道这对每个人都不起作用,但我觉得有趣的是,只需添加一个 Divider 就可以解决这个问题。

        struct DividerTest: View {
        var body: some View {
            VStack(alignment: .leading) {
                Text("Foo")
                Text("Bar")
                Divider()
            }.background(Color.red)
          }
        }
        

        【讨论】:

          【解决方案10】:

          这是一段有用的代码:

          extension View {
              func expandable () -> some View {
                  ZStack {
                      Color.clear
                      self
                  }
              }
          }
          

          比较使用和不使用.expandable() 修饰符的结果:

          Text("hello")
              .background(Color.blue)
          

          -

          Text("hello")
              .expandable()
              .background(Color.blue)
          

          【讨论】:

            【解决方案11】:

            您可以在方便的扩展中使用 GeometryReader 来填充父对象

            extension View {
                func fillParent(alignment:Alignment = .center) -> some View {
                    return GeometryReader { geometry in
                        self
                            .frame(width: geometry.size.width,
                                   height: geometry.size.height,
                                   alignment: alignment)
                    }
                }
            }
            

            所以使用请求的示例,您会得到

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

            (注意不再需要垫片)

            【讨论】:

            • 这不是全屏。
            • 问题没有要求全屏,它要求全宽。如果要超出安全区域,请使用 . edgeIgnoringSafeArea
            【解决方案12】:

            使用SwiftUI设计登录页面

            import SwiftUI
            
            struct ContentView: View {
            
                @State var email: String = "william@gmail.com"
                @State var password: String = ""
                @State static var labelTitle: String = ""
            
            
                var body: some View {
                    VStack(alignment: .center){
                        //Label
                        Text("Login").font(.largeTitle).foregroundColor(.yellow).bold()
                        //TextField
                        TextField("Email", text: $email)
                            .textContentType(.emailAddress)
                            .foregroundColor(.blue)
                            .frame(minHeight: 40)
                            .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))
            
                        TextField("Password", text: $password) //Placeholder
                            .textContentType(.newPassword)
                            .frame(minHeight: 40)
                            .foregroundColor(.blue) // Text color
                            .background(RoundedRectangle(cornerRadius: 10).foregroundColor(Color.green))
            
                        //Button
                        Button(action: {
            
                        }) {
                            HStack {
                                Image(uiImage: UIImage(named: "Login")!)
                                    .renderingMode(.original)
                                    .font(.title)
                                    .foregroundColor(.blue)
                                Text("Login")
                                    .font(.title)
                                    .foregroundColor(.white)
                            }
            
            
                            .font(.headline)
                            .frame(minWidth: 0, maxWidth: .infinity)
                            .background(LinearGradient(gradient: Gradient(colors: [Color("DarkGreen"), Color("LightGreen")]), startPoint: .leading, endPoint: .trailing))
                            .cornerRadius(40)
                            .padding(.horizontal, 20)
            
                            .frame(width: 200, height: 50, alignment: .center)
                        }
                        Spacer()
            
                    }.padding(10)
            
                        .frame(minWidth: 0, idealWidth: .infinity, maxWidth: .infinity, minHeight: 0, idealHeight: .infinity, maxHeight: .infinity, alignment: .top)
                        .background(Color.gray)
            
                }
            
            }
            
            struct ContentView_Previews: PreviewProvider {
                static var previews: some View {
                    ContentView()
                }
            }
            

            【讨论】:

              【解决方案13】:

              对于 Swift 5.2 和 iOS 13.4,根据您的需要,您可以使用以下示例之一将您的 VStack 与顶部前导约束和全尺寸框架对齐。

              请注意,下面的代码 sn-ps 都导致相同的显示,但不保证VStack 的有效框架,也不保证调试视图层次结构时可能出现的View 元素的数量。


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

              最简单的方法是将VStack 的框架设置为最大宽度和高度,并在frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) 中传递所需的对齐方式:

              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。使用Spacers 强制对齐

              您可以将 VStack 嵌入到完整大小的 HStack 中,并使用尾随和底部 Spacers 强制您的 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 有以下declaration

              将其内容定义为自身大小和坐标空间的函数的容器视图。 [...] 这个视图返回一个灵活的首选尺寸给它的父布局。

              下面的代码 sn-p 显示了如何使用 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
                          )
                  }
              
              }
              

              显示:

              【讨论】:

              • 这应该被接受为答案。
              【解决方案14】:

              一个没有“装置”的好解决方案是被遗忘的ZStack

              ZStack(alignment: .top){
                  Color.red
                  VStack{
                      Text("Hello World").font(.title)
                      Text("Another").font(.body)
                  }
              }
              

              结果:

              【讨论】:

                【解决方案15】:

                这是另一种可以节省项目时间的方法:

                与其他不可重复使用的答案相比,代码更少且可重复使用!


                extension View {
                    
                    var maxedOut: some View {
                        
                        return Color.clear
                            .overlay(self, alignment: .center)
                
                    }
                    
                    func maxedOut(color: Color = Color.clear, alignment: Alignment = Alignment.center) -> some View {
                        
                        return color
                            .overlay(self, alignment: alignment)
                        
                    }
                    
                }
                

                用例:

                struct ContentView: View {
                
                    var body: some View {
                
                        Text("Hello, World!")
                            .maxedOut
                            .background(Color.blue)
                        
                        Text("Hello, World!")
                            .maxedOut(color: Color.red)
                  
                    }
                }
                

                【讨论】:

                  猜你喜欢
                  • 2020-04-24
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-09-21
                  • 2020-09-21
                  • 1970-01-01
                  • 2014-04-21
                  • 2012-07-29
                  相关资源
                  最近更新 更多