【问题标题】:UIScrollView Zoom Does Not Work With AutolayoutUIScrollView 缩放不适用于自动布局
【发布时间】:2013-01-04 04:21:04
【问题描述】:

使用严格的自动布局环境使用 UIScrollView 进行缩放似乎不起作用。

这尤其令人沮丧,因为 iOS 6 发行说明确实让我相信,当我在 http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html987654321@ 上写到“纯自动布局方法”时,我应该相信这一点。

我查看了 WWDC 2012 的第 202、228 和 232 场会议的幻灯片,但没有找到答案。

我在互联网上看到的唯一一个专门针对这个问题的问题是UIScrollView zooming with Auto Layout,但它没有提供问题的代码,也没有答案。

这位用户https://stackoverflow.com/users/341994/matt 对 UIScrollView 自动布局问题给出了很多很好的回应,甚至链接到 git hub 上的代码,但我在那里找不到任何可以回答这个问题的东西。

我已尝试将这个问题归结为最低限度以使其清楚。

我创建了一个带有情节提要的新单视图应用程序,并且没有在界面构建器中进行任何更改。

我在项目“pic.jpg”中添加了一个大图片文件。

SVFViewController.h

#import <UIKit/UIKit.h> 
@interface SVFViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic) UIImageView *imageViewPointer;
@end

SVFViewController.m

#import "SVFViewController.h"

@interface SVFViewController ()

@end

@implementation SVFViewController


- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    UIImageView *imageView = [[UIImageView alloc] init];
    [imageView setImage:[UIImage imageNamed:@"pic.jpg"]];
    [self.view addSubview:scrollView];
    [scrollView addSubview:imageView];

    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    self.imageViewPointer = imageView;
    scrollView.maximumZoomScale = 2;
    scrollView.minimumZoomScale = .5;
    scrollView.delegate = self;

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
    NSLog(@"Current views dictionary: %@", viewsDictionary);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
}

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

@end

请注意,我特别努力使它与 iOS 6 发行说明中提供的示例代码一样,只是为了实现缩放做了最少的工作。

那么,问题来了?

当您运行此应用程序并在滚动视图中平移时,一切都很好。但是当你缩放时问题很明显,图像来回闪烁,并且每次缩放时图像在滚动视图中的位置都会变得更加错误。

看起来 imageView 的内容偏移量正在进行战斗,似乎每次“缩放”都会被两个不同的东西设置为不同的值。 (imageView 的内容偏移属性的 NSLog 似乎证实了这一点)。

我在这里做错了什么?有谁知道如何在纯自动布局环境中的 UIScrollView 中实现缩放。那里有这样的例子吗?

请帮忙。

【问题讨论】:

  • 我遇到了这个 (developer.apple.com/library/ios/#samplecode/PhotoScroller/…),这比回答我的问题更让我担心。是的,它使用自动布局实现 UIScrollView 缩放,但它是通过以一种我无法理解的复杂方式子类化 UIScrollView 来实现的。必须有一种更简单的方法来做到这一点......
  • 我遇到了同样的问题。在 UIScrollView 中嵌入 UIImageView 并不像现在使用自动布局那样简单。

标签: ios uiscrollview zooming autolayout


【解决方案1】:

再次重读iOS SDK 6.0 release notes发现:

请注意,您可以通过在视图和滚动视图的子树之外的视图(例如滚动视图的超级视图)之间创建约束,使滚动视图的子视图看起来浮动(不滚动)在其他滚动内容之上。

解决方案

将您的子视图连接到外部视图。换句话说,就是嵌入了滚动视图的视图。

并以以下方式应用约束,我已经成功了:

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

【讨论】:

  • 我尝试实施此解决方案,但在缩放时我收到“无法同时满足约束”消息。我希望我可以进一步调试,但不幸的是我不明白你的方法。
  • 感谢您的反馈。我已经更新了答案。事实上,一个约束('|')是多余的。
  • 是的,这不合逻辑且难以理解,但它确实有效。看起来这是一种Apple错误。希望他们将来会改变这种 UIScrollView 行为。
  • 这是一个简单的项目,我希望,正如你所期望的那样。 dl.dropbox.com/u/52101940/Test.zip
  • 谢谢马克!您链接的项目确实有效!我还没有弄清楚为什么该项目有效,而上面问题中记录的那个还没有。我想这与您指出的指标位有关。我会继续关注的。
【解决方案2】:

出现的问题是在缩放过程中图像视图的位置发生变化。在缩放过程中,imageview 的原点位置将变为负值。我相信这就是为什么会发生生涩的运动。同样,缩放完成后,imageView 仍然在错误的位置,这意味着滚动会出现偏移。

如果你实现-(void) scrollViewDidZoom:(UIScrollView *)scrollView 并在这段时间内记录 UIImageView 的框架,你可以看到它的原点发生了变化。

我最终通过实施this 之类的策略解决了问题

除了在缩放时更改 contentView 的框架

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.contentView.frame;
    cFrame.origin = CGPointZero;
    self.contentView.frame = cFrame;
}

【讨论】:

  • 感谢您的评论!不幸的是,我仍然无法根据您链接的策略使上面记录的简单照片示例起作用。
  • 仅供参考 - github 链接已损坏
【解决方案3】:

这些解决方案都很有效。这是我所做的,不需要任何黑客或子类,使用此设置:

[view]
  [scrollView]
    [container]
      [imageView]
      [imageView2]
  1. 在 IB 中,将 scrollView 的顶部、前导、底部和尾部连接到 view
  2. container 的顶部、前导、底部和尾部连接到scrollView
  3. container 的 center-x 和 center-y 连接到 scrollView 的 center-x 和 center-y 并将其标记为 在构建时删除 .这只需要使 IB 中的警告静音。
  4. 在视图控制器中实现viewForZoomingInScrollView:,它应该是scrollView的委托,并从该方法返回container
  5. 在设置imageView的图像时,确定最小缩放比例(因为现在它将以原始大小显示)并设置它:

    CGSize mySize = self.view.bounds.size;
    CGSize imgSize = _imageView.image.size;
    CGFloat scale = fminf(mySize.width / imgSize.width,
                          mySize.height / imgSize.height);
    _scrollView.minimumZoomScale = scale;
    _scrollView.zoomScale = scale;
    _scrollView.maximumZoomScale = 4 * scale;
    

这对我有用,在设置图像时会缩放滚动视图以显示整个图像并允许放大到初始大小的 4 倍。

【讨论】:

  • 嗯...我喜欢你的方法。但是告诉我,为什么容器内有 2 个 imageViews?另外,您不必将它们连接到某些东西吗?他们的限制是什么?
  • 这只是一个示例,您可以向container 添加任何内容(因为这是正在缩放的​​视图)。然而,这些图像视图的限制是您需要它们,以便所有方面都连接起来。
  • 是的,我无法让它在 iOS7 或 iOS8 上运行。发生了一些奇怪的事情,图像不在 UIScrollView 中居中。另外,当放大和缩小时,事情会完全出错......它对你有用吗?
  • 我必须保持中心约束,以使其正常工作(iOS 11)。
  • 漂亮!谢谢,我认为固定中心将是多余的,因为约束已经锚定在四个边缘上,但在缩放的情况下它完美地清理了行为!
【解决方案4】:

假设您在“UIView”内的“UIScrollView”内有故事板“UIImageView”。

将“UIScrollView”中的所有约束与视图控制器 + UIView 中的两个约束(Horizontal Space - Scroll View - View & Horizontal Space - Scroll View - View)链接。

为“UIScrollView”设置视图控制器 AS 委托。

然后实现这段代码:

@interface VC () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray* constraints;
@end

@implementation FamilyTreeImageVC

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView removeConstraints:self.constraints];
}


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

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.imageView.frame;
    cFrame.origin = CGPointZero;
    self.imageView.frame = cFrame;
}

【讨论】:

  • 在我的情况下,我不需要在 UIView 中链接水平空间,但其余的修复它!
  • @Hizabr 为什么要删除 viewDidLoad 中的约束?
【解决方案5】:

我在尝试仅使用 scrollView 从情节提要的项目中实现缩放时遇到了同样的问题。

我通过添加单独的捏合手势识别器来修复它。我只是将它从工具箱拖到我的场景中。然后我将它连接到一个我称为“doPinch”的动作,它实现了缩放。我将它连接到一个我称为“pinchRecognizer”的插座,以便我可以访问它的 scale 属性。这似乎覆盖了滚动视图的内置缩放,并且跳跃消失了。也许它不会在起源上犯同样的错误,或者更优雅地处理它。在 IB 布局之上的工作很少。

将捏合手势识别器引入场景后,您确实需要 action 和 viewForZoomingInScrollView 方法。错过任何一个,缩放就会停止工作。

我的视图控制器中的代码是这样的:

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

- (IBAction)doPinch:(id)sender

{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}

这个非常基本的实现确实有一个副作用:当您返回缩放图像并再缩放一些时,scale 的值为 1.0f,因此它会跳回原始比例。

您可以通过引入属性“currentScale”来跟踪缩放并在再次开始缩放时设置捏合手势识别器缩放来解决此问题。您需要使用手势识别器的 state 属性:

- (IBAction)doPinch:(id)sender
{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
NSLog(@"Gesture recognizer state is: %d", self.pinchRecognizer.state);

switch (self.pinchRecognizer.state)
{
    case 1:
        NSLog(@"Zoom begins, with current scale set to: %f", self.currentScale);
        [self.pinchRecognizer setScale:self.currentScale];
        break;
    case 3:
        NSLog(@"Zoom ends, with pinch recognizer scale set to: %f", self.pinchRecognizer.scale);
        self.currentScale = self.pinchRecognizer.scale;
    default:
        break;
}

[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}

【讨论】:

  • 夹点到底是什么?滚动视图?图像视图?整体看法?
  • 很抱歉延迟回复 - 未能找到收件箱!捏合手势识别器被添加到情节提要的场景中。它似乎位于顶层并检测到该场景中任何可见视图的夹点。
【解决方案6】:

所以这就是我设法解决的问题。

这是我修改后的原文:

@interface ScrollViewZoomTestViewController () <UIScrollViewDelegate>

@property (nonatomic, strong) UIImageView* imageViewPointer;

// These properties are new
@property (nonatomic, strong) NSMutableArray* imageViewConstraints;
@property (nonatomic) BOOL imageViewConstraintsNeedUpdating;
@property (nonatomic, strong) UIScrollView* scrollViewPointer;

@end

@implementation ScrollViewZoomTestViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    UIImageView *imageView = [[UIImageView alloc] init];
    [imageView setImage:[UIImage imageNamed:@"pic.jpg"]];
    [self.view addSubview:scrollView];
    [scrollView addSubview:imageView];

    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    self.imageViewPointer = imageView;
    // New
    self.scrollViewPointer = scrollView;
    scrollView.maximumZoomScale = 2;
    scrollView.minimumZoomScale = .5;
    scrollView.delegate = self;

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
    NSLog(@"Current views dictionary: %@", viewsDictionary);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];

    // Saving the image view width & height constraints
    self.imageViewConstraints = [[NSMutableArray alloc] init];
    // Constrain the image view to be the same width & height of the scroll view
    [_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
    [_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];

    // Add the image view constraints to the VIEW, not the scroll view
    [self.view addConstraints:_imageViewConstraints];

    // Flag
    self.imageViewConstraintsNeedUpdating = YES;
}

所以在这里回顾一下,我将所有约束添加到 self.view,将在 UIImageView 上设置的约束保存在 NSMutableArray 属性中,并设置 UIImageView 约束需要更新的标志。

UIImageView 上的这些初始约束可以从一开始就设置它。它将与UIScrollView 具有相同的宽度和高度。但是,这不允许我们缩放图像视图。它将保持与滚动视图相同的width / height。不是我们想要的。这就是我保存约束并设置标志的原因。我们会在一分钟内解决这个问题。

现在,设置缩放视图:

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

好的,现在我们需要实际允许我们缩放。我正在删除最初的 UIImageView 约束并添加一些新的约束,这一次约束到 UIScrollViewcontentSize widthheight

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
    if(_imageViewConstraintsNeedUpdating)
    {
        // Remove the previous image view constraints
        [self.view removeConstraints:_imageViewConstraints];

        // Replace them with new ones, this time constraining against the `width` & `height` of the scroll view's content, not the scroll view itself
        NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_scrollViewPointer, _imageViewPointer);
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_imageViewPointer(width)]|" options:0 metrics:@{@"width" : @(_scrollViewPointer.contentSize.width)} views:viewsDictionary]];
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_imageViewPointer(height)]|" options:0 metrics:@{@"height" : @(_scrollViewPointer.contentSize.height)} views:viewsDictionary]];

        self.imageViewConstraintsNeedUpdating = NO;
    }
}

@end

我们不能像这样在-viewDidLoad 中设置约束,因为图像还没有被渲染到UIImageView,所以UIScrollViewcontentSize 将是{0,0}

这对我来说似乎很老套,但它确实有效,它确实使用了纯自动布局,我找不到更好的方法来做到这一点。在我看来,Apple 需要提供一种更好的方法来缩放 UIScrollView 中的内容并使用自动布局约束。

【讨论】:

    猜你喜欢
    • 2012-12-11
    • 2014-11-27
    • 1970-01-01
    • 2013-11-07
    • 2013-01-06
    • 2015-11-18
    • 1970-01-01
    • 2015-05-28
    • 1970-01-01
    相关资源
    最近更新 更多