当内容适合滚动视图边界时,SwiftUI ScrollView 不会使内容居中
Posted
技术标签:
【中文标题】当内容适合滚动视图边界时,SwiftUI ScrollView 不会使内容居中【英文标题】:SwiftUI ScrollView does not center content when content fits scrollview bounds 【发布时间】: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)
//
【问题讨论】:
【参考方案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
。
使用VStack
或HStack
可以使内容居中。
因为只设置了最小高度,如果需要,内容仍然可以比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)
【讨论】:
以上是关于当内容适合滚动视图边界时,SwiftUI ScrollView 不会使内容居中的主要内容,如果未能解决你的问题,请参考以下文章
滚动视图中的内容作为列表项在滚动(swiftui)时消失,为啥?