具有动态大小内容的 UIScrollView
UIScrollView with dynamically sized content
(Xcode 11, Swift)
作为 iOS 和 Autolayout 的新手,我正在努力实现一个相当简单的 (恕我直言) 视图,该视图显示一个 [垂直] 项目列表。唯一的问题是项目是动态决定的,它们中的每一个都可以是文本或图像(其中任何一个都可能相当大,因此需要滚动)。 WebView 不是一个选项,所以它必须在本机实现。
我是这样理解这个过程的:
- 在 IB 中创建一个 UIScrollView 并将其调整为外框的大小。
- 创建一个容器视图作为 UIScrollView 的子视图(同样是在 IB 中),并设置相同的大小。
- 对两者的等宽设置约束
- 在运行时,使用 UILabels/UIImageViews 填充容器视图并以编程方式设置约束以确保正确的布局。
- "Tell" 关于子视图高度的滚动视图,以使其管理其滚动。
这是正确的方法吗?它似乎对我不起作用(对于动态添加非常高的图像到容器视图的玩具示例 - 我无法让滚动工作)。在上面的过程中执行最后一步的正确方法是什么 - 只需将滚动视图的 contentSize 强制为填充的容器视图的大小(它似乎对我不起作用)。任何帮助将不胜感激。
对齐约束(leading/trailing/top/bottom)
滚动视图和内容视图之间的对齐约束定义了scrollable range of the content
。 例如,
- 如果scrollView.bottom = contentView.bottom,表示Scroll View是
可滚动到内容视图的底部。
- 如果scrollView.bottom = contentView.bottom + 100, 可滚动
滚动视图的底端将超过内容视图的末尾 100
点.
- 如果scrollView.bottom = contentView.bottom — 100,底部
即使 scrollView 滚动到,也不会到达内容视图
底端。
即ScrollView上的(bottom)anchor表示外框的(bottom)边缘,即ContentView的可见部分;内容视图上的(底部)锚点指的是实际内容的边缘,如果不滚动到该边缘,它将被隐藏。
与正常用例不同,滚动视图和内容视图之间的对齐约束与内容视图的实际大小无关。它们仅影响“内容视图的可滚动范围”,而不影响“实际内容”尺寸”。 Content View的实际大小必须另外定义。
大小限制 (width/height)
要实际调整内容视图的大小,我们可以将内容视图的大小设置为特定长度,例如 width/height 500。如果 width/height 超过滚动视图的 width/height ,会有一个滚动条供用户滚动。
然而,更常见的情况是,我们希望内容视图与滚动视图具有相同的宽度(或高度)。在这种情况下,我们将有
contentView.width = scrollView.width
内容视图的宽度是指内容的实际全宽。另一方面,ScrollView的宽度是指ScrollView的外容器框宽度。当然也可以不等宽,可以是a * scrollView.width + b等其他形式。
如果我们的内容视图比滚动视图更高(或更宽),则会出现滚动条。
Content View不仅可以是单view,也可以是多view,只要对Scroll View使用对齐方式和大小约束进行适当的约束即可。
详情可以关注这篇文章:Link.
在 运行 时向滚动视图添加多个元素时,您可能会发现使用 UIStackView
更容易...如果设置正确,它会自动增加高度每个添加的对象。
作为一个简单的例子...
1) 首先添加一个 UIScrollView
(我给它设置了蓝色背景以便于查看)。在所有 4 个边上将其限制为零:
请注意,我们看到 "red circle" 表示缺少/冲突约束。暂时忽略它。
2) 添加一个UIView
作为"content view" 到滚动视图(我给它一个systemYellow 背景以便于查看)。根据 Content Layout Guide,将其在所有 4 个边上都限制为零——这将(最终)定义滚动视图的内容大小。还要将其约束为等宽等高 Frame Layout Guide:
重要步骤: Select 高度限制,并在 Size Inspector
窗格中 select Placeholder - Remove at build time
复选框。这将在设计时满足 IB 中的自动布局,但将允许该视图的高度根据需要缩小/增大。
3) 添加垂直 UIStackView
到 "content view"。在所有 4 个边上将其约束为零。配置其属性为Fill / Fill / 8
(如下图):
4) 添加一个 @IBOutlet
连接到视图控制器中的堆栈视图 class。现在,在 运行 时间,当您将 UI 元素添加到堆栈视图时,所有 "scrollability" 都将由自动布局处理。
这是一个例子class:
class DynaScrollViewController: UIViewController {
@IBOutlet var theStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// local var so we can reuse it
var theLabel = UILabel()
var theImageView = UIImageView()
// create a new label
theLabel = UILabel()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theLabel.translatesAutoresizingMaskIntoConstraints = false
// multi-line
theLabel.numberOfLines = 0
// cyan background to make it easy to see
theLabel.backgroundColor = .cyan
// add 9 lines of text to the label
theLabel.text = (1...9).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .yellow
// add 5 lines of text to the label
theLabel.text = (1...5).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// create a new UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load an image for it - I have one named background
if let img = UIImage(named: "background") {
theImageView.image = img
}
// let's give the image view a 4:3 width:height ratio
theImageView.widthAnchor.constraint(equalTo: theImageView.heightAnchor, multiplier: 4.0/3.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .green
// add 2 lines of text to the label
theLabel.text = (1...2).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load a different image for it - I have one named AquariumBG
if let img = UIImage(named: "AquariumBG") {
theImageView.image = img
}
// let's give this image view a 1:1 width:height ratio
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor, multiplier: 1.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
}
}
如果按照这些步骤操作,您应该得到以下输出:
并且,滚动到底部后:
(Xcode 11, Swift)
作为 iOS 和 Autolayout 的新手,我正在努力实现一个相当简单的 (恕我直言) 视图,该视图显示一个 [垂直] 项目列表。唯一的问题是项目是动态决定的,它们中的每一个都可以是文本或图像(其中任何一个都可能相当大,因此需要滚动)。 WebView 不是一个选项,所以它必须在本机实现。
我是这样理解这个过程的:
- 在 IB 中创建一个 UIScrollView 并将其调整为外框的大小。
- 创建一个容器视图作为 UIScrollView 的子视图(同样是在 IB 中),并设置相同的大小。
- 对两者的等宽设置约束
- 在运行时,使用 UILabels/UIImageViews 填充容器视图并以编程方式设置约束以确保正确的布局。
- "Tell" 关于子视图高度的滚动视图,以使其管理其滚动。
这是正确的方法吗?它似乎对我不起作用(对于动态添加非常高的图像到容器视图的玩具示例 - 我无法让滚动工作)。在上面的过程中执行最后一步的正确方法是什么 - 只需将滚动视图的 contentSize 强制为填充的容器视图的大小(它似乎对我不起作用)。任何帮助将不胜感激。
对齐约束(leading/trailing/top/bottom)
滚动视图和内容视图之间的对齐约束定义了scrollable range of the content
。 例如,
- 如果scrollView.bottom = contentView.bottom,表示Scroll View是 可滚动到内容视图的底部。
- 如果scrollView.bottom = contentView.bottom + 100, 可滚动 滚动视图的底端将超过内容视图的末尾 100 点.
- 如果scrollView.bottom = contentView.bottom — 100,底部 即使 scrollView 滚动到,也不会到达内容视图 底端。
即ScrollView上的(bottom)anchor表示外框的(bottom)边缘,即ContentView的可见部分;内容视图上的(底部)锚点指的是实际内容的边缘,如果不滚动到该边缘,它将被隐藏。 与正常用例不同,滚动视图和内容视图之间的对齐约束与内容视图的实际大小无关。它们仅影响“内容视图的可滚动范围”,而不影响“实际内容”尺寸”。 Content View的实际大小必须另外定义。
大小限制 (width/height)
要实际调整内容视图的大小,我们可以将内容视图的大小设置为特定长度,例如 width/height 500。如果 width/height 超过滚动视图的 width/height ,会有一个滚动条供用户滚动。 然而,更常见的情况是,我们希望内容视图与滚动视图具有相同的宽度(或高度)。在这种情况下,我们将有
contentView.width = scrollView.width
内容视图的宽度是指内容的实际全宽。另一方面,ScrollView的宽度是指ScrollView的外容器框宽度。当然也可以不等宽,可以是a * scrollView.width + b等其他形式。 如果我们的内容视图比滚动视图更高(或更宽),则会出现滚动条。 Content View不仅可以是单view,也可以是多view,只要对Scroll View使用对齐方式和大小约束进行适当的约束即可。
详情可以关注这篇文章:Link.
在 运行 时向滚动视图添加多个元素时,您可能会发现使用 UIStackView
更容易...如果设置正确,它会自动增加高度每个添加的对象。
作为一个简单的例子...
1) 首先添加一个 UIScrollView
(我给它设置了蓝色背景以便于查看)。在所有 4 个边上将其限制为零:
请注意,我们看到 "red circle" 表示缺少/冲突约束。暂时忽略它。
2) 添加一个UIView
作为"content view" 到滚动视图(我给它一个systemYellow 背景以便于查看)。根据 Content Layout Guide,将其在所有 4 个边上都限制为零——这将(最终)定义滚动视图的内容大小。还要将其约束为等宽等高 Frame Layout Guide:
重要步骤: Select 高度限制,并在 Size Inspector
窗格中 select Placeholder - Remove at build time
复选框。这将在设计时满足 IB 中的自动布局,但将允许该视图的高度根据需要缩小/增大。
3) 添加垂直 UIStackView
到 "content view"。在所有 4 个边上将其约束为零。配置其属性为Fill / Fill / 8
(如下图):
4) 添加一个 @IBOutlet
连接到视图控制器中的堆栈视图 class。现在,在 运行 时间,当您将 UI 元素添加到堆栈视图时,所有 "scrollability" 都将由自动布局处理。
这是一个例子class:
class DynaScrollViewController: UIViewController {
@IBOutlet var theStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
// local var so we can reuse it
var theLabel = UILabel()
var theImageView = UIImageView()
// create a new label
theLabel = UILabel()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theLabel.translatesAutoresizingMaskIntoConstraints = false
// multi-line
theLabel.numberOfLines = 0
// cyan background to make it easy to see
theLabel.backgroundColor = .cyan
// add 9 lines of text to the label
theLabel.text = (1...9).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .yellow
// add 5 lines of text to the label
theLabel.text = (1...5).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// create a new UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load an image for it - I have one named background
if let img = UIImage(named: "background") {
theImageView.image = img
}
// let's give the image view a 4:3 width:height ratio
theImageView.widthAnchor.constraint(equalTo: theImageView.heightAnchor, multiplier: 4.0/3.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
// add another label
theLabel = UILabel()
// multi-line
theLabel.numberOfLines = 0
// yellow background to make it easy to see
theLabel.backgroundColor = .green
// add 2 lines of text to the label
theLabel.text = (1...2).map({ "Line \([=10=])" }).joined(separator: "\n")
// add it to the stack view
theStackView.addArrangedSubview(theLabel)
// add another UIImageView
theImageView = UIImageView()
// this gets set to false when the label is added to a stack view,
// but good to get in the habit of setting it
theImageView.translatesAutoresizingMaskIntoConstraints = false
// load a different image for it - I have one named AquariumBG
if let img = UIImage(named: "AquariumBG") {
theImageView.image = img
}
// let's give this image view a 1:1 width:height ratio
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor, multiplier: 1.0).isActive = true
// add it to the stack view
theStackView.addArrangedSubview(theImageView)
}
}
如果按照这些步骤操作,您应该得到以下输出:
并且,滚动到底部后: