如何在使用自动布局时使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在使用滚动视图时建议采用以下方法:

  1. 将滚动视图添加到场景中。
  2. 绘制约束以正常定义滚动视图的大小和位置。
  3. 将视图添加到滚动视图。 将视图的Xcode特定标签设置为Content View。
  4. 将内容视图的顶部,底部,前导和尾部边缘固定到滚动视图的相应边缘。 内容视图现在定义滚动视图的内容区域。
  5. (……)
  6. (……)
  7. 在内容视图中布置滚动视图的内容。 使用约束将内容定位在内容视图中。

在您的情况下, GridView必须位于内容视图中

Interface Builder

您可以保留您一直使用的网格视图约束,但现在,它已附加到内容视图。 对于仅水平缩放,请保持代码完全不变。 你的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) }