首页 文章

UIScrollView缩放不适用于Autolayout

提问于
浏览
36

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

这尤其令人沮丧,因为iOS 6发行说明肯定让我相信它应该在撰写关于"Pure Auto Layout approach"这里的时候http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html

我查看了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中实现属性缩放 . 在那里的任何地方都有这样的例子吗?

请帮忙 .

6 回答

  • 12

    再一次,重新阅读iOS SDK 6.0 release notes我发现:

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

    解决方案

    Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.

    并以下列方式应用约束我已经得到了它的工作:

    [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]];
    
  • 9

    出现的问题是在缩放过程中更改imageview的位置 . 在缩放过程中,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;
    }
    
  • 3

    这些解决方案都有点工作 . 以下是我所做的,不需要黑客或子类,使用此设置:

    [view]
      [scrollView]
        [container]
          [imageView]
          [imageView2]
    
    • 在IB中,将 scrollView 的顶部,前导,底部和尾部连接到 view .

    • container 的顶部,前导,底部和尾部连接到 scrollView .

    • container 的center-x和center-y连接到 scrollView and mark it as remove on build time 的center-x和center-y . 这只需要消除IB中的警告 .

    • 在视图控制器中实现 viewForZoomingInScrollView: ,它应该是scrollView的委托,并从该方法返回 container .

    • 设置 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倍 .

  • 0

    假设你有故事板“ UIImageView " inside " UIScrollView " inside " UIView ” .

    将“ UIScrollView ”中的所有约束与视图控制器链接为UIView中的两个约束( Horizontal Space - Scroll View - ViewHorizontal 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;
    }
    
  • 0

    尝试使用scrollView从故事板项目实现缩放时,我遇到了同样的问题 .

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

    一旦将捏合手势识别器引入场景,您就需要动作和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];
    }
    
  • 1

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

    这是我的更改原件:

    @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中,在NSMutableArray属性中保存UIImageView上设置的约束,并设置UIImageView约束需要更新的标志 .

    UIImageView的这些初始约束可以将其设置为开始 . 它将与UIScrollView具有相同的宽度和高度 . 但是,这不允许我们缩放图像视图 . 它将保持与滚动视图相同的宽度/高度 . 不是我们想要的 . 这就是为什么我要保存约束并设置标志 . 我们会在一分钟内处理好这件事 .

    现在,设置缩放视图:

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

    好的,现在我们需要实际允许我们缩放 . 我正在删除最初的UIImageView约束并添加一些新约束,这次约束到UIScrollView的contentSize宽度和高度:

    - (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中,因此UIScrollView的contentSize将为{0,0} .

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

相关问题