使用自动布局时,如何使UIScrollView仅向一个方向放大

时间:2016-02-05 22:14:41

标签: ios swift uiscrollview autolayout zoom

我尝试制作UIScrollView只允许在水平方向上进行缩放。滚动视图使用纯自动布局方法设置。通常的方法是在许多Stack Overflow答案(例如this one)和other资源中建议的。在自动布局存在之前,我已成功在应用中使用此功能。方法如下:在滚动视图的内容视图中,我覆盖setTransform(),并将传入的变换修改为仅在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)
}

当不使用autolayout时,这非常有效:

Zooming without autolayout

但是,使用自动布局时,内容偏移在缩放过程中会导致错误。虽然内容视图仅在水平方向上缩放,但它会垂直移动:

Zooming with autolayout

我已经放置了一个示例项目on GitHub(用于制作此问题中的视频)。它包含两个故事板,Main.storyboard和NoAutolayout.storyboard,它们是相同的,除了Main.storyboard已打开autolayout而NoAutolayout没有。通过更改主界面项目设置在它们之间切换,您可以在两种情况下看到行为。

我真的不想关闭autolayout,因为它解决了以比手动布局要求更好的方式实现我的UI的许多问题。有没有办法在使用自动布局进行缩放时保持垂直内容偏移正确(即零)?

编辑:我已将第三个故事板AutolayoutVariableColumns.storyboard添加到示例项目中。这会添加一个文本字段来更改GridView中的列数,从而更改其内在内容大小。这更加密切地显示了在提示这个问题的真实应用程序中我需要的行为。

4 个答案:

答案 0 :(得分:9)

想想我弄清楚了。您需要在变换中应用平移以抵消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()期间抓取帧大小并假设它包含未校正的高度。可能有一些边缘情况不正确;可能是视图更新周期中更好的地方来计算高度,但这是基本的想法。

答案 1 :(得分:3)

尝试设置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

  

幸运的是,有一面旗帜。它被称为translatesAutoResizingMask IntoConstraints [没有空格]。

     

它有点拗口,但它几乎完全符合它的说法。它使视图的行为与他们在Legacy Layout系统下的行为方式相同,但在Auto Layout世界中。

答案 2 :(得分:1)

尽管@ Anna的解决方案有效,但 UIScrollView 提供了一种使用自动布局的方法。但是因为滚动视图与其他视图的工作方式略有不同,所以约束也有不同的解释:

  • 滚动视图的边缘/边距与其内容之间的约束附加到滚动视图的内容区域
  • 高度宽度中心之间的约束附加到滚动视图的框架
  • 滚动视图与滚动视图外部视图之间的约束与普通视图类似。

因此,当您将子视图添加到固定到其边缘/边距的滚动视图时,该子视图将成为滚动视图的内容区域或内容视图

Apple在Working with Scroll Views中建议采用以下方法:

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

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

Interface Builder

您可以保留您一直使用的网格视图约束,但现在已附加到内容视图。对于仅水平缩放,请保持代码完全不变。你的transform压倒一切就好了。

答案 3 :(得分:0)

我不确定这是否满足您对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)
}