当内容适合滚动视图范围时,SwiftUI ScrollView不会使内容居中

时间:2020-04-13 07:58:46

标签: swiftui scrollview

我正在尝试使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)
//        }
    }

enter image description here

3 个答案:

答案 0 :(得分:3)

您添加的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)
        }
    }
}

答案 1 :(得分:3)

信用去@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 Solution Image

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

为什么起作用?

ScrollView

首先,让我们了解SwiftUI的ScrollView的工作原理。

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

居中对齐

这就是内容居中的原因。

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

答案 2 :(得分:1)

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

demo

// 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)
        }
    }
}