【问题标题】:SwiftUI ScrollView does not center content when content fits scrollview bounds当内容适合滚动视图边界时,SwiftUI ScrollView 不会使内容居中
【发布时间】:2020-04-13 07:58:46
【问题描述】:

我试图让ScrollView 中的内容在该内容小到不需要滚动时居中,而是与顶部对齐。这是一个错误还是我错过了添加一些东西?使用 Xcode 11.4 (11E146)

    @State private var count : Int = 100

    var body : some View {
//        VStack {
            ScrollView {
                VStack {
                    Button(action: {
                        if self.count > 99 {
                            self.count = 5
                        } else {
                            self.count = 100
                        }
                    }) {
                        Text("CLICK")
                    }
                    ForEach(0...count, id: \.self) { no in
                        Text("entry: \(no)")
                    }
                }
                .padding(8)
                .border(Color.red)
                .frame(alignment: .center)
            }
            .border(Color.blue)
            .padding(8)
//        }
    }

【问题讨论】:

    标签: swiftui scrollview


    【解决方案1】:

    感谢@Thaniel 找到解决方案。我的目的是更全面地解释幕后发生的事情,以揭开 SwiftUI 的神秘面纱并解释解决方案为何有效。

    解决方案

    ScrollView 包裹在GeometryReader 中,这样您就可以设置可滚动内容的最小高度(或宽度,如果滚动视图是水平的)以匹配ScrollView 的高度。这将使可滚动区域的尺寸永远不会小于ScrollView 的尺寸。您还可以声明一个静态维度并使用它来设置ScrollView 及其内容的高度。

    动态高度

    @State private var count : Int = 5
    
    var body: some View {
    
        // use GeometryReader to dynamically get the ScrollView height
        GeometryReader { geometry in
            ScrollView {
                VStack(alignment: .leading) {
                    ForEach(0...self.count, id: \.self) { num in
                        Text("entry: \(num)")
                    }
                }
                .padding(10)
                // border is drawn before the height is changed
                .border(Color.red)
                // match the content height with the ScrollView height and let the VStack center the content
                .frame(minHeight: geometry.size.height)
            }
            .border(Color.blue)
        }
    
    }
    

    静态高度

    @State private var count : Int = 5
    // set a static height
    private let scrollViewHeight: CGFloat = 800
    
    var body: some View {
    
        ScrollView {
            VStack(alignment: .leading) {
                ForEach(0...self.count, id: \.self) { num in
                    Text("entry: \(num)")
                }
            }
            .padding(10)
            // border is drawn before the height is changed
            .border(Color.red)
            // match the content height with the ScrollView height and let the VStack center the content
            .frame(minHeight: scrollViewHeight)
        }
        .border(Color.blue)
    
    }
    

    内容的边界似乎小于ScrollView,如红色边框所示。发生这种情况是因为框架是在绘制边框之后设置的。它还说明了内容的默认大小小于ScrollView的事实。

    为什么有效?

    滚动视图

    首先,让我们了解一下 SwiftUI 的 ScrollView 是如何工作的。

    • ScrollView 将其内容包装在一个名为 ScrollViewContentContainer 的子元素中。
    • ScrollViewContentContainer 始终与ScrollView 的顶部或前缘对齐,具体取决于它是否可沿垂直轴或水平轴滚动,或两者兼而有之。
    • ScrollViewContentContainer 根据 ScrollView 内容调整自身大小。
    • 当内容小于ScrollView 时,ScrollViewContentContainer 会将其推到顶部或前沿。

    居中对齐

    这就是内容居中的原因。

    • 该解决方案依赖于强制 ScrollViewContentContainer 与其父 ScrollView 具有相同的宽度和高度。
    • GeometryReader 可用于动态获取ScrollView 的高度,也可以声明静态尺寸,以便ScrollView 及其内容可以使用相同的参数来设置它们的水平或垂直尺寸。
    • ScrollView 内容使用.frame(minWidth:,minHeight:) 方法可确保它永远不会小于ScrollView
    • 使用VStackHStack 可以使内容居中。
    • 因为只设置了最小高度,如果需要,内容仍然可以比ScrollView 更大,并且ScrollViewContentContainer 保留其与顶部或前缘对齐的默认行为。

    【讨论】:

    • 这就是答案的解释方式。谢谢!
    【解决方案2】:

    您添加的frame(alignment: .center) 修饰符不起作用,因为它所做的是将您的视图包装在一个大小完全相同的新视图中。因此,对齐不会做任何事情,因为没有额外的空间来重新定位视图。

    您的问题的一个潜在解决方案是将整个ScrollView 包装在GeometryReader 中以读取可用高度。然后使用该高度来指定子项不应小于它。这将使您的视图以ScrollView 为中心。

    struct ContentView: View {
        @State private var count : Int = 100
    
        var body : some View {
            GeometryReader { geometry in
                ScrollView {
                    VStack {
                        Button(action: {
                            if self.count > 99 {
                                self.count = 5
                            } else {
                                self.count = 100
                            }
                        }) {
                            Text("CLICK")
                        }
                        ForEach(0...self.count, id: \.self) { no in
                            Text("entry: \(no)")
                        }
                    }
                    .padding(8)
                    .border(Color.red)
                    .frame(minHeight: geometry.size.height) // Here we are setting minimum height for the content
                }
                .border(Color.blue)
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      您观察到的只是正常的 ScrollView 行为。这是实现目标的可能方法的演示。

      // view pref to detect internal content height
      struct ViewHeightKey: PreferenceKey {
          typealias Value = CGFloat
          static var defaultValue: CGFloat { 0 }
          static func reduce(value: inout Value, nextValue: () -> Value) {
              value = value + nextValue()
          }
      }
      
      // extension for modifier to detect view height
      extension ViewHeightKey: ViewModifier {
          func body(content: Content) -> some View {
              return content.background(GeometryReader { proxy in
                  Color.clear.preference(key: Self.self, value: proxy.size.height)
              })
          }
      }
      
      // Modified your view for demo
      struct TestAdjustedScrollView: View {
          @State private var count : Int = 100
      
          @State private var myHeight: CGFloat? = nil
          var body : some View {
              GeometryReader { gp in
                  ScrollView {
                      VStack {
                          Button(action: {
                              if self.count > 99 {
                                  self.count = 5
                              } else {
                                  self.count = 100
                              }
                          }) {
                              Text("CLICK")
                          }
                          ForEach(0...self.count, id: \.self) { no in
                              Text("entry: \(no)")
                          }
                      }
                      .padding(8)
                      .border(Color.red)
                      .frame(alignment: .center)
                      .modifier(ViewHeightKey())   // read content view height !!
                  }
                  .onPreferenceChange(ViewHeightKey.self) {
                      // handle content view height
                      self.myHeight = $0 < gp.size.height ? $0 : gp.size.height
                  }
                  .frame(height: self.myHeight) // align own height with content
                  .border(Color.blue)
                  .padding(8)
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2020-01-27
        • 2020-11-12
        • 1970-01-01
        • 2021-07-18
        • 2013-11-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多