如何在使用自动布局时使UIScrollView仅向一个方向放大
我试图让UIScrollView只允许在水平方向上放大。 滚动视图使用纯自动布局方法设置。 通常的方法是在许多Stack Overflow答案(例如这个 )和其他资源中建议的。 在自动布局存在之前,我已成功在应用中使用此function。 方法如下:在滚动视图的内容视图中,我覆盖setTransform()
,并将传入的变换修改为仅在x方向上缩放:
override var transform: CGAffineTransform { get { return super.transform } set { var t = newValue td = 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) }
不使用autolayout时,这非常好用:
但是,使用自动布局时,内容偏移在缩放期间会导致错误。 内容视图仅在水平方向上缩放 ,但它会垂直移动:
我在GitHub上放了一个示例项目(用于在这个问题中制作video)。 它包含两个故事板,Main.storyboard和NoAutolayout.storyboard,它们是相同的,除了Main.storyboard已打开autolayout而NoAutolayout没有。 通过更改主界面项目设置在它们之间切换,您可以在两种情况下查看行为。
我真的不想关闭autolayout,因为它解决了以比手动布局要求更好的方式实现我的UI的一些问题。 有没有办法在自动布局缩放期间保持垂直内容偏移正确(即零)?
编辑:我已经在示例项目中添加了第三个故事板AutolayoutVariableColumns.storyboard。 这会添加一个文本字段来更改GridView中的列数,从而更改其内在内容大小。 这更加密切地显示了在提示此问题的真实应用程序中我需要的行为。
我想我想通了。 您需要在变换中应用平移以抵消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 td = 1.0 t.ty = (1.0 - ta) * unzoomedViewHeight/2 super.transform = t } } }
要计算变换,我们需要知道视图的未校正高度。 在这里,我只是在layoutSubviews()期间抓取帧大小并假设它包含未校正的高度。 可能有一些边缘情况不正确; 可能是视图更新周期中更好的位置来计算高度,但这是基本的想法。
尝试设置translatesAutoresizingMaskIntoConstraints
func scrollViewDidZoom(scrollView: UIScrollView) { gridView.translatesAutoresizingMaskIntoConstraints = true }
在示例项目中它可以工作。 如果这还不够,请尝试在scrollViewDidEndZooming:
编程方式创建新约束scrollViewDidEndZooming:
可能会有所帮助。
此外,如果这没有帮助,请更新示例项目,以便我们可以使用变量intrinsicContentSize()
重现问题
Ole Begemann的这篇文章给了我很多帮助
我如何学会不再担心和喜欢cocoa汽车布局
WWDC 2015video
汽车布局之谜,第1部分
汽车布局之谜,第2部分
幸运的是,有一面旗帜。 它被称为translatesAutoResizingMask IntoConstraints [没有空间]。
它有点拗口,但它几乎完全符合它的说法。 它使视图的行为与他们在Legacy Layout系统下的行为方式相同,但在Auto Layout世界中。
虽然@Anna的解决方案有效,但UIScrollView提供了一种使用Auto Layout的方法 。 但是因为滚动视图与其他视图的工作方式略有不同,所以约束也有不同的解释:
- 滚动视图的边缘/边距与其内容之间的约束附加到滚动视图的内容区域 。
- 高度 , 宽度或中心之间的约束附加到滚动视图的框架 。
- 滚动视图和滚动视图外部视图之间的约束与普通视图类似。
因此,当您将子视图添加到固定到其边/边距的滚动视图时,该子视图将成为滚动视图的内容区域或内容视图 。
Apple在使用滚动视图时建议采用以下方法:
- 将滚动视图添加到场景中。
- 绘制约束以正常定义滚动视图的大小和位置。
- 将视图添加到滚动视图。 将视图的Xcode特定标签设置为Content View。
- 将内容视图的顶部,底部,前导和尾部边缘固定到滚动视图的相应边缘。 内容视图现在定义滚动视图的内容区域。
- (……)
- (……)
- 在内容视图中布置滚动视图的内容。 使用约束将内容定位在内容视图中。
在您的情况下, GridView必须位于内容视图中 :
您可以保留您一直使用的网格视图约束,但现在,它已附加到内容视图。 对于仅水平缩放,请保持代码完全不变。 你的transform
覆盖处理得非常好。
我不确定这是否满足您对100%自动布局的要求,但这是一个解决方案,其中UIScrollView
设置为autolayout并将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) }