当内容适合滚动视图边界时,SwiftUI ScrollView 不会将内容居中
SwiftUI ScrollView does not center content when content fits scrollview bounds
我试图让 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)
// }
}
您观察到正常的 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 = [=10=] < gp.size.height ? [=10=] : gp.size.height
}
.frame(height: self.myHeight) // align own height with content
.border(Color.blue)
.padding(8)
}
}
}
您添加的 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)
}
}
}
归功于@Thaniel 找到解决方案。我在这里的目的是更全面地解释幕后发生的事情,以揭开 SwiftUI 的神秘面纱,并解释该解决方案为何有效。
解决方案
将 ScrollView
包裹在 GeometryReader
中,这样您就可以设置可滚动内容的最小高度(如果滚动视图是水平的,则为宽度)以匹配 [=12] 的高度=].这将使可滚动区域的尺寸永远不会小于 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
.
- 使用
VStack
或 HStack
可以使内容居中。
- 因为只设置了最小高度,如果需要,内容仍然可以比
ScrollView
更大,并且 ScrollViewContentContainer
保留其与顶部或前沿对齐的默认行为。
对我来说,GeometryReader 无论如何都会将内容对齐到顶部。我通过添加两个额外的垫片 () 解决了这个问题:
GeometryReader { metrics in
ScrollView {
VStack(spacing: 0) {
Spacer()
// your content goes here
Spacer()
}
.frame(minHeight: metrics.size.height)
}
}
我试图让 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)
// }
}
您观察到正常的 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 = [=10=] < gp.size.height ? [=10=] : gp.size.height
}
.frame(height: self.myHeight) // align own height with content
.border(Color.blue)
.padding(8)
}
}
}
您添加的 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)
}
}
}
归功于@Thaniel 找到解决方案。我在这里的目的是更全面地解释幕后发生的事情,以揭开 SwiftUI 的神秘面纱,并解释该解决方案为何有效。
解决方案
将 ScrollView
包裹在 GeometryReader
中,这样您就可以设置可滚动内容的最小高度(如果滚动视图是水平的,则为宽度)以匹配 [=12] 的高度=].这将使可滚动区域的尺寸永远不会小于 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
. - 使用
VStack
或HStack
可以使内容居中。 - 因为只设置了最小高度,如果需要,内容仍然可以比
ScrollView
更大,并且ScrollViewContentContainer
保留其与顶部或前沿对齐的默认行为。
对我来说,GeometryReader 无论如何都会将内容对齐到顶部。我通过添加两个额外的垫片 (
GeometryReader { metrics in
ScrollView {
VStack(spacing: 0) {
Spacer()
// your content goes here
Spacer()
}
.frame(minHeight: metrics.size.height)
}
}