UIScrollView自动布局缩放

32

我正在尝试使用自动布局实现 UIScrollView新方法。我已经设置了从内部视图到滚动视图的约束,以便它可以自动计算其自己的 contentSize,这个功能非常好用——但是当我尝试缩放时,情况变得一团糟。我无法准确地描述发生了什么,除了说内部视图被“搞砸”了。

你可以在这里看到此行为的示例(不是我的项目;在缩放之前,您必须设置滚动视图的maximumZoomScale并实现-viewForZoomingInScrollView:才能起作用)。

有其他人遇到过这种行为吗?目前是否有任何方法可以让自动布局下的UIScrollView进行缩放,而不必重新实现缩放行为?


我知道你的意思。我也遇到了同样的问题。你解决了吗?似乎与约束有关,我尝试在scrollViewWillBeginZooming:withView:中删除约束,在scrollViewDidEndZooming:withView:atScale:中重新添加约束,在缩放期间它运行得更好,但缩放后内容位置不正确。 - devguydavid
1
@david:不,我从未解决过这个问题。我的结论是UIKit的各个部分目前还不兼容自动布局,因此在这些情况下(特别是UIScrollView和UIPageViewController),我已经停止使用自动布局。 - phu
我也有这个问题。我有一个包含imageView的滚动视图,并且正在尝试仅允许对图像进行简单缩放,但是它会在捏视图时发生奇怪的事情,并扭曲整个imageView并跳来跳去地改变大小。希望有人能找到解决方法。 - JimmyJammed
3个回答

4
我看到的最好的答案是Mark's(https://stackoverflow.com/users/1051919/mark-kryzhanouski),在这里发布:UIScrollView Zoom Does Not Work With Autolayout

关键在于,您必须将嵌套在滚动视图中的图像视图锚定到滚动视图的父级。尽管iOS 6发行说明提供了指导,但我认为哪个视图“浮动”在哪个视图上并不直观。 在这种情况下,滚动视图只是一个单独的图像视图。

我确实进行了很多尝试,希望找到全IB方法,但没有找到。 您仍然可以在IB中生成视图层次结构,但仍然必须以编程方式添加约束。 您可以删除一些或所有默认约束(主要是为了消除约束冲突警告),但始终需要Mark的代码来将图像视图与滚动视图的父级(图像视图的祖父级)绑定。

看起来应该比这更简单-它“应该只是工作”,但:

NSDictionary *viewsDictionary = @{ @"scrollView": self.scrollView, @"imageView": self.imageView };
[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"H:|[imageView(width)]"
    options:0
    metrics:@{@"width": @(self.imageView.image.size.width)}
    views:viewsDictionary]];

[self.view addConstraints:[NSLayoutConstraint
    constraintsWithVisualFormat:@"V:|[imageView(height)]"
    options:0
    metrics:@{@"height": @(self.imageView.image.size.height)}
    views:viewsDictionary]];

当你说“必须将嵌套在滚动视图中的图像视图锚定到滚动视图的父级”时,是否需要执行任何操作才能实现此行为?因为我已经按照你所说的以编程方式设置了约束,但仍然无法使其正常工作。 - Yaman

3

在Storyboard中没有添加imageView的情况下,我发现以下内容完美地解决了问题:

-(UIImageView *)imageView
{
    if (!_imageView) _imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
    return _imageView;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.scrollView addSubview:self.imageView];
    // Set the min and max:
    self.scrollView.minimumZoomScale = 0.2;
    self.scrollView.maximumZoomScale = 5.0;
    self.scrollView.delegate = self;

    // Set the content:
    self.scrollView.zoomScale = 1.0; // reset zoomScale for new image
    self.scrollView.contentSize = CGSizeMake(image.size.width/2, image.size.height/2);
    self.imageView.frame = CGRectMake(0, 0, image.size.width/2, image.size.height/2);
    self.imageView.image = image;
}

-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return self.imageView;
}

5
你手动设置了滚动视图的contentSize而不是用自动布局约束,这也是问题的重点。请查看我发布的第一个链接,其中有对我所说内容的描述(搜索“Pure Auto Layout approach”短语)。 - phu
追求“纯粹”有什么意义呢?我只是提供了一个解决方案。 - AMayes
7
也许他需要使用自动布局(Autolayout),并需要一个适用于该功能的解决方案? - Andrew Theis

2

完整的 Swift Playground 示例

我能想到的最简单的示例是将 UIImageView 添加到 UIScrollView 中。这是100%的代码,您只需要向 Playground 添加一个 PNG 图像。我把我的命名为 Image.png。在 Playground 中,您将在“实时视图”中看到整个内容呈现。使用 Ctrl + 单击来放大缩小,然后拖动屏幕。在内容被放大到比屏幕更大之前,平移不起作用。双击图像可在1x和3x缩放之间切换。

基于苹果公司的技术笔记 TN2154:UIScrollView 和 Autolayout

注意事项

如果您的内容不大于屏幕大小,您会发现整个过程非常令人沮丧。如果您的内容完全适合屏幕,则不会发生任何事情。这就是为什么您也必须使缩放工作。如果您想证明它有效,请使用真正大的图像(比窗口大)进行测试。

import UIKit
import PlaygroundSupport

enum TapToggle {
    case Regular, Large
}

class ScrollingViewController : UIViewController
{
    var tapToggle: TapToggle = .Large
    var scrollView: UIScrollView?
    var imageView: UIImageView?

    override func viewDidLoad()
    {
        let image = UIImage(named: "Image")
        let imageView = UIImageView(image: image)
        imageView.translatesAutoresizingMaskIntoConstraints = false
        imageView.backgroundColor = .white
        imageView.isUserInteractionEnabled = true

        let scrollView = UIScrollView()
        scrollView.minimumZoomScale = 0.5
        scrollView.maximumZoomScale = 10.0
        scrollView.delegate = self
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(imageView)
        let imageViewKey = "imageView"
        let imageViews = [imageViewKey: imageView]
        scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[\(imageViewKey)]|", options: [], metrics: nil, views: imageViews))
        scrollView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[\(imageViewKey)]|", options: [], metrics: nil, views: imageViews))
        self.imageView = imageView

        scrollView.backgroundColor = .white
        self.view.addSubview(scrollView)

        let scrollViewKey = "scrollView"
        let scrollViews = [scrollViewKey: scrollView]
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[\(scrollViewKey)]|", options: [], metrics: nil, views: scrollViews))
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[\(scrollViewKey)]|", options: [], metrics: nil, views: scrollViews))

        self.scrollView = scrollView

        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap(sender:)))
        tapGesture.numberOfTapsRequired = 2
        self.imageView?.addGestureRecognizer(tapGesture)
    }

    @objc
    public func didDoubleTap(sender: AnyObject)
    {
        switch self.tapToggle {
        case .Regular:
            self.scrollView?.zoomScale = 1.0
            self.tapToggle = .Large
        case .Large:
            self.scrollView?.zoomScale = 3.0
            self.tapToggle = .Regular
        }
    }
}

extension ScrollingViewController: UIScrollViewDelegate
{
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return self.imageView
    }

    func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
    {
        print("\(scale)")
    }
}

PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = ScrollingViewController()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接