使用自动布局时如何使 UIScrollView 仅在一个方向上缩放
How to make UIScrollView zoom in only one direction when using auto layout
我正在尝试制作一个 UIScrollView 只允许在水平方向上缩放。滚动视图是使用纯自动布局方法设置的。通常的方法是在许多 Stack Overflow 答案中建议的(例如 this one) and other 资源。在自动布局存在之前,我已经在应用程序中成功地使用了它。方法如下:在滚动视图的内容视图中,我覆盖 setTransform()
,修改传入的transform只在x方向缩放:
override var transform: CGAffineTransform {
get { return super.transform }
set {
var t = newValue
t.d = 1.0
super.transform = t
}
}
我还重置了滚动视图的内容偏移量,使其在缩放期间不会垂直滚动:
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
}
这在不使用自动布局时效果很好:
但是,当使用自动布局时,内容偏移在缩放过程中最终会出错。虽然内容视图仅在水平方向 缩放 ,但它在垂直方向移动:
我放了一个示例项目on GitHub(用来制作这个问题的视频)。它包含两个故事板,Main.storyboard 和 NoAutolayout.storyboard,除了 Main.storyboard 启用了自动布局而 NoAutolayout 没有启用之外,它们是相同的。通过更改主界面项目设置在它们之间切换,您可以看到两种情况下的行为。
我真的不想关闭自动布局,因为它以比手动布局所需的更好的方式解决了实现我的 UI 的许多问题。有没有办法在使用自动布局缩放期间保持垂直内容偏移正确(即零)?
编辑:我在示例项目中添加了第三个故事板 AutolayoutVariableColumns.storyboard。这会添加一个文本字段来更改 GridView 中的列数,从而更改其固有内容的大小。这更接近地显示了我在提示此问题的真实应用程序中需要的行为。
我不确定这是否满足您对 100% 自动布局的要求,但这是一个解决方案,其中 UIScrollView
设置了自动布局,并且 gridView
作为子视图添加到它。
您的 ViewController.swift
应如下所示:
// Add an outlet for the scrollView in interface builder.
@IBOutlet weak var scrollView: UIScrollView!
// Remove the outlet and view for gridView.
//@IBOutlet var gridView: GridView!
// Create the gridView here:
var gridView: GridView!
// Setup some views
override func viewDidLoad() {
super.viewDidLoad()
// The width for the gridView is set to 1000 here, change if needed.
gridView = GridView(frame: CGRect(x: 0, y: 0, width: 1000, height: self.view.bounds.height))
scrollView.addSubview(gridView)
scrollView.contentSize = CGSize(width: gridView.frame.width, height: scrollView.frame.height)
gridView.contentMode = .ScaleAspectFill
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return gridView
}
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
}
尝试设置translatesAutoresizingMaskIntoConstraints
func scrollViewDidZoom(scrollView: UIScrollView) {
gridView.translatesAutoresizingMaskIntoConstraints = true
}
在示例项目中它有效。如果这还不够,尝试在 scrollViewDidEndZooming:
中以编程方式创建新约束可能会有所帮助。
此外,如果这没有帮助,请更新示例项目,以便我们可以使用变量重现问题 intrinsicContentSize()
Ole Begemann 的这篇文章 对我帮助很大
How I Learned to Stop Worrying and Love Cocoa Auto Layout
WWDC 2015 视频
Mysteries of Auto Layout, Part 1
Mysteries of Auto Layout, Part 2
And so luckily, there's a flag for that. It's called translatesAutoResizingMask IntoConstraints [without space].
It's a bit of a mouthful, but it pretty much does what it says. It makes views behave the way that they did under the Legacy Layout system but in an Auto Layout world.
我想我明白了。您需要在转换中应用平移,以抵消 UIScrollView 在缩放时尝试执行的居中操作。
将此添加到您的 GridView:
var unzoomedViewHeight: CGFloat?
override func layoutSubviews() {
super.layoutSubviews()
unzoomedViewHeight = frame.size.height
}
override var transform: CGAffineTransform {
get { return super.transform }
set {
if let unzoomedViewHeight = unzoomedViewHeight {
var t = newValue
t.d = 1.0
t.ty = (1.0 - t.a) * unzoomedViewHeight/2
super.transform = t
}
}
}
要计算变换,我们需要知道视图的未缩放高度。在这里,我只是在 layoutSubviews() 期间获取帧大小并假设它包含未缩放的高度。可能有一些边缘情况是不正确的;在视图更新周期中计算高度可能是一个更好的地方,但这是基本思想。
虽然@Anna 的解决方案有效,但 UIScrollView 提供了一种使用 Auto Layout 的方法。但是因为滚动视图与其他视图的工作方式略有不同,所以对约束的解释也不同:
- 滚动视图的 edges/margins 及其 内容之间的约束 附加到滚动视图的 内容面积.
- 高度、宽度或中心之间的约束附加到滚动视图的帧.
- 滚动视图和滚动视图外的视图之间的约束与普通视图一样。
因此,当您将子视图添加到固定到其 edges/margins 的滚动视图时,该子视图成为滚动视图的内容区域或 内容视图。
Apple 在 Working with Scroll Views 中建议采用以下方法:
- Add the scroll view to the scene.
- Draw constraints to define the scroll view’s size and position, as normal.
- Add a view to the scroll view. Set the view’s Xcode specific label to Content View.
- Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines
the scroll view’s content area.
- (...)
- (...)
- Lay out the scroll view’s content inside the content view. Use constraints to position the content inside the content view as normal.
在您的情况下,GridView 必须位于 content 视图中:
您可以保留一直在使用但现在附加到内容视图的网格视图约束。对于仅水平缩放,请保持代码原样。您的 transform
覆盖处理得很好。
我正在尝试制作一个 UIScrollView 只允许在水平方向上缩放。滚动视图是使用纯自动布局方法设置的。通常的方法是在许多 Stack Overflow 答案中建议的(例如 this one) and other 资源。在自动布局存在之前,我已经在应用程序中成功地使用了它。方法如下:在滚动视图的内容视图中,我覆盖 setTransform()
,修改传入的transform只在x方向缩放:
override var transform: CGAffineTransform {
get { return super.transform }
set {
var t = newValue
t.d = 1.0
super.transform = t
}
}
我还重置了滚动视图的内容偏移量,使其在缩放期间不会垂直滚动:
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
}
这在不使用自动布局时效果很好:
但是,当使用自动布局时,内容偏移在缩放过程中最终会出错。虽然内容视图仅在水平方向 缩放 ,但它在垂直方向移动:
我放了一个示例项目on GitHub(用来制作这个问题的视频)。它包含两个故事板,Main.storyboard 和 NoAutolayout.storyboard,除了 Main.storyboard 启用了自动布局而 NoAutolayout 没有启用之外,它们是相同的。通过更改主界面项目设置在它们之间切换,您可以看到两种情况下的行为。
我真的不想关闭自动布局,因为它以比手动布局所需的更好的方式解决了实现我的 UI 的许多问题。有没有办法在使用自动布局缩放期间保持垂直内容偏移正确(即零)?
编辑:我在示例项目中添加了第三个故事板 AutolayoutVariableColumns.storyboard。这会添加一个文本字段来更改 GridView 中的列数,从而更改其固有内容的大小。这更接近地显示了我在提示此问题的真实应用程序中需要的行为。
我不确定这是否满足您对 100% 自动布局的要求,但这是一个解决方案,其中 UIScrollView
设置了自动布局,并且 gridView
作为子视图添加到它。
您的 ViewController.swift
应如下所示:
// Add an outlet for the scrollView in interface builder.
@IBOutlet weak var scrollView: UIScrollView!
// Remove the outlet and view for gridView.
//@IBOutlet var gridView: GridView!
// Create the gridView here:
var gridView: GridView!
// Setup some views
override func viewDidLoad() {
super.viewDidLoad()
// The width for the gridView is set to 1000 here, change if needed.
gridView = GridView(frame: CGRect(x: 0, y: 0, width: 1000, height: self.view.bounds.height))
scrollView.addSubview(gridView)
scrollView.contentSize = CGSize(width: gridView.frame.width, height: scrollView.frame.height)
gridView.contentMode = .ScaleAspectFill
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return gridView
}
func scrollViewDidZoom(scrollView: UIScrollView) {
scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x, y: 0.0)
scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: scrollView.frame.height)
}
尝试设置translatesAutoresizingMaskIntoConstraints
func scrollViewDidZoom(scrollView: UIScrollView) {
gridView.translatesAutoresizingMaskIntoConstraints = true
}
在示例项目中它有效。如果这还不够,尝试在 scrollViewDidEndZooming:
中以编程方式创建新约束可能会有所帮助。
此外,如果这没有帮助,请更新示例项目,以便我们可以使用变量重现问题 intrinsicContentSize()
Ole Begemann 的这篇文章 对我帮助很大
How I Learned to Stop Worrying and Love Cocoa Auto Layout
WWDC 2015 视频
Mysteries of Auto Layout, Part 1
Mysteries of Auto Layout, Part 2
And so luckily, there's a flag for that. It's called translatesAutoResizingMask IntoConstraints [without space].
It's a bit of a mouthful, but it pretty much does what it says. It makes views behave the way that they did under the Legacy Layout system but in an Auto Layout world.
我想我明白了。您需要在转换中应用平移,以抵消 UIScrollView 在缩放时尝试执行的居中操作。
将此添加到您的 GridView:
var unzoomedViewHeight: CGFloat?
override func layoutSubviews() {
super.layoutSubviews()
unzoomedViewHeight = frame.size.height
}
override var transform: CGAffineTransform {
get { return super.transform }
set {
if let unzoomedViewHeight = unzoomedViewHeight {
var t = newValue
t.d = 1.0
t.ty = (1.0 - t.a) * unzoomedViewHeight/2
super.transform = t
}
}
}
要计算变换,我们需要知道视图的未缩放高度。在这里,我只是在 layoutSubviews() 期间获取帧大小并假设它包含未缩放的高度。可能有一些边缘情况是不正确的;在视图更新周期中计算高度可能是一个更好的地方,但这是基本思想。
虽然@Anna 的解决方案有效,但 UIScrollView 提供了一种使用 Auto Layout 的方法。但是因为滚动视图与其他视图的工作方式略有不同,所以对约束的解释也不同:
- 滚动视图的 edges/margins 及其 内容之间的约束 附加到滚动视图的 内容面积.
- 高度、宽度或中心之间的约束附加到滚动视图的帧.
- 滚动视图和滚动视图外的视图之间的约束与普通视图一样。
因此,当您将子视图添加到固定到其 edges/margins 的滚动视图时,该子视图成为滚动视图的内容区域或 内容视图。
Apple 在 Working with Scroll Views 中建议采用以下方法:
- Add the scroll view to the scene.
- Draw constraints to define the scroll view’s size and position, as normal.
- Add a view to the scroll view. Set the view’s Xcode specific label to Content View.
- Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines the scroll view’s content area.
- (...)
- (...)
- Lay out the scroll view’s content inside the content view. Use constraints to position the content inside the content view as normal.
在您的情况下,GridView 必须位于 content 视图中:
您可以保留一直在使用但现在附加到内容视图的网格视图约束。对于仅水平缩放,请保持代码原样。您的 transform
覆盖处理得很好。