UIScrollView - 什么时候设置 contentSize

UIScrollView - when is contentSize set

我有一个 UIViewController,它的视图层次结构如下所示:

我有将图像视图定位在滚动视图框架中间的代码,如下所示:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self recenterContent:scrollView];
}

- (void)recenterContent:(UIScrollView *)scrollView {
    //this centers the content when it is smaller than the scrollView's bounds
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
    
    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}

这在缩放内容时工作正常,但当视图控制器首次加载时它不会居中。这是因为 scrollView.contentSize 总是 0。所以我的问题是 - 在 scrollView.contentSize 设置后我应该什么时候调用这个方法?什么时候设置?

我在 viewDidLayoutSubviews 中尝试过,然后设置了滚动视图的边界,但没有设置内容大小。有什么方法可以保证滚动视图设置内容大小吗?

或者当图像小于滚动视图时,是否有更好的方法使图像居中?我想要完成的是让图像视图不在滚动视图的顶部并且我正在使用的是有效的,除非未设置滚动视图的内容大小时。但是,如果有更好的方法可以做到这一点而无需调整 contentInset,我也可以接受。


更新

这是我目前拥有的。

它几乎可以正常工作,但无论我尝试什么,我都无法在加载视图时让它看起来正确。它现在的工作方式是它一开始是偏离中心的,因为当它调用 recenterContent 方法时,在显示视图之前,滚动视图的内容大小是 CGSizeZero,所以计算是错误的。但是,如果我尝试在显示视图后将内容重新居中,那么在它居中之前会有明显的延迟。

如果我使用 AutoLayout 约束来指定大小,我对何时设置滚动视图的 contentSize 感到困惑。

这是我的代码。任何人都可以看到它有什么问题吗?

@interface MyImageViewController ()

@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) UIImageView *imageView;
@property (assign, nonatomic) BOOL needsZoomScale;

@end

@implementation MyImageViewController

- (void)loadView {
    self.view = [[UIView alloc] init];
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.imageView];
    
    self.needsZoomScale = YES;
    
    [NSLayoutConstraint activateConstraints:@[
        [self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
        [self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
        
        [self.imageView.leadingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.leadingAnchor],
        [self.imageView.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor],
        [self.imageView.trailingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.trailingAnchor],
        [self.imageView.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor]
    ]];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapZoom:)];
    doubleTapGesture.numberOfTapsRequired = 2;
    [self.imageView addGestureRecognizer:doubleTapGesture];
}

- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
    CGRect zoomRect;
    
    //the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
    //of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
    //grows.
    zoomRect.size.width = scrollView.frame.size.width / scale;
    zoomRect.size.height = scrollView.frame.size.height / scale;
    
    //choose an origin so as to get the right center
    zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
    zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
    
    return zoomRect;
}

- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
    UIView *tappedView = sender.view;
    CGPoint tappedPoint = [sender locationInView:tappedView];
    
    if (tappedPoint.x <= 0) {
        tappedPoint.x = 1;
    }
    
    if (tappedPoint.y <= 0) {
        tappedPoint.y = 1;
    }
    
    if (tappedPoint.x >= tappedView.bounds.size.width) {
        tappedPoint.x = tappedView.bounds.size.width - 1;
    }
    
    if (tappedPoint.y >= tappedView.bounds.size.height) {
        tappedPoint.y = tappedView.bounds.size.height - 1;
    }

    CGFloat zoomScale;
    if (self.scrollView.zoomScale < 1) {
        zoomScale = 1;
    } else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
        zoomScale = self.scrollView.maximumZoomScale;
    } else {
        zoomScale = self.scrollView.minimumZoomScale;
    }
    
    CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
    
    [self.scrollView zoomToRect:zoomRect animated:YES];
}

- (UIScrollView *)scrollView {
    if (!self->_scrollView) {
        self->_scrollView = [[UIScrollView alloc] init];
        self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
        self->_scrollView.minimumZoomScale = 0.1f;
        self->_scrollView.maximumZoomScale = 4.0f;
        self->_scrollView.bounces = YES;
        self->_scrollView.bouncesZoom = YES;
        self->_scrollView.delegate = self;
        self->_scrollView.backgroundColor = [UIColor blackColor];
    }
    return self->_scrollView;
}

- (UIImageView *)imageView {
    if (!self->_imageView) {
        self->_imageView = [[UIImageView alloc] init];
        self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
        self->_imageView.userInteractionEnabled = YES;
    }
    return self->_imageView;
}

- (UIImage *)image {
    return self.imageView.image;
}

- (void)setImage:(UIImage *)image {
    self.imageView.image = image;
    self.needsZoomScale = YES;
    [self updateZoomScale];
}

- (void)updateZoomScale {
    if (self.needsZoomScale && self.image) {
        CGSize size = self.view.bounds.size;
        
        if (size.width == 0.0f || size.height == 0.0f) {
            return;
        }
            
        UIImage *image = self.image;
        CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
        if (imageSize.width > 0 && imageSize.height > 0) {
            CGFloat widthScale = size.width / imageSize.width;
            CGFloat heightScale = size.height / imageSize.height;
            CGFloat minScale = MIN(widthScale, heightScale);
                
            self.scrollView.minimumZoomScale = minScale;
            self.scrollView.zoomScale = minScale;
            self.needsZoomScale = NO;
        }
    }
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    [self updateZoomScale];
}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    [self recenterContent:self.scrollView];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self recenterContent:self.scrollView];
}

#pragma mark - UIScrollViewDelegate

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

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self recenterContent:scrollView];
}

- (void)recenterContent:(UIScrollView *)scrollView {
    //this centers the content when it is smaller than the scrollView's bounds
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
    
    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}

@end

问题是 UIImageView 的固有内容大小为 0,0 -- 因此您的代码最初是将 0x0 图像视图放在滚动视图的中心.

我对您发布的代码做了一些更改...查看评论(我将更改“包装”在

// ---------------------------------

评论行:

@interface MyImageViewController : UIViewController <UIScrollViewDelegate>
@end

@interface MyImageViewController ()

@property (strong, nonatomic) UIScrollView *scrollView;
@property (strong, nonatomic) UIImageView *imageView;
@property (assign, nonatomic) BOOL needsZoomScale;

@end

@implementation MyImageViewController

- (void)loadView {
    self.view = [[UIView alloc] init];
    [self.view addSubview:self.scrollView];
    [self.scrollView addSubview:self.imageView];

    self.needsZoomScale = YES;
    
    // ---------------------------------
    //  respect safe area
    UILayoutGuide *g = [self.view safeAreaLayoutGuide];
    //  saves on a little typing
    UILayoutGuide *sg = [self.scrollView contentLayoutGuide];
    // ---------------------------------

    [NSLayoutConstraint activateConstraints:@[
        [self.scrollView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor],
        [self.scrollView.topAnchor constraintEqualToAnchor:g.topAnchor],
        [self.scrollView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor],
        [self.scrollView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor],
        
        [self.imageView.leadingAnchor constraintEqualToAnchor:sg.leadingAnchor],
        [self.imageView.topAnchor constraintEqualToAnchor:sg.topAnchor],
        [self.imageView.trailingAnchor constraintEqualToAnchor:sg.trailingAnchor],
        [self.imageView.bottomAnchor constraintEqualToAnchor:sg.bottomAnchor]
    ]];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapZoom:)];
    doubleTapGesture.numberOfTapsRequired = 2;
    [self.imageView addGestureRecognizer:doubleTapGesture];
}

- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
    CGRect zoomRect;
    
    //the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
    //of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
    //grows.
    zoomRect.size.width = scrollView.frame.size.width / scale;
    zoomRect.size.height = scrollView.frame.size.height / scale;
    
    //choose an origin so as to get the right center
    zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
    zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
    
    return zoomRect;
}

- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
    UIView *tappedView = sender.view;
    CGPoint tappedPoint = [sender locationInView:tappedView];
    
    if (tappedPoint.x <= 0) {
        tappedPoint.x = 1;
    }
    
    if (tappedPoint.y <= 0) {
        tappedPoint.y = 1;
    }
    
    if (tappedPoint.x >= tappedView.bounds.size.width) {
        tappedPoint.x = tappedView.bounds.size.width - 1;
    }
    
    if (tappedPoint.y >= tappedView.bounds.size.height) {
        tappedPoint.y = tappedView.bounds.size.height - 1;
    }
    
    CGFloat zoomScale;
    if (self.scrollView.zoomScale < 1) {
        zoomScale = 1;
    } else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
        zoomScale = self.scrollView.maximumZoomScale;
    } else {
        zoomScale = self.scrollView.minimumZoomScale;
    }
    
    CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
    
    [self.scrollView zoomToRect:zoomRect animated:YES];
}

- (UIScrollView *)scrollView {
    if (!self->_scrollView) {
        self->_scrollView = [[UIScrollView alloc] init];
        self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
        self->_scrollView.minimumZoomScale = 0.1f;
        self->_scrollView.maximumZoomScale = 4.0f;
        self->_scrollView.bounces = YES;
        self->_scrollView.bouncesZoom = YES;
        self->_scrollView.delegate = self;
        self->_scrollView.backgroundColor = [UIColor blackColor];
    }
    return self->_scrollView;
}

- (UIImageView *)imageView {
    if (!self->_imageView) {
        self->_imageView = [[UIImageView alloc] init];
        self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
        self->_imageView.userInteractionEnabled = YES;
    }
    return self->_imageView;
}

- (UIImage *)image {
    return self.imageView.image;
}

- (void)setImage:(UIImage *)image {
    self.imageView.image = image;
    
    // ---------------------------------
    //  set the frame here
    self.imageView.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
    
    // ---------------------------------
    //  not needed ... unless maybe changing the image while view is showing?
    //self.needsZoomScale = YES;
    //[self updateZoomScale];
}

- (void)updateZoomScale {
    if (self.needsZoomScale && self.image) {
        CGSize size = self.view.bounds.size;
        
        if (size.width == 0.0f || size.height == 0.0f) {
            return;
        }
        
        UIImage *image = self.image;
        CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
        if (imageSize.width > 0 && imageSize.height > 0) {
            CGFloat widthScale = size.width / imageSize.width;
            CGFloat heightScale = size.height / imageSize.height;
            CGFloat minScale = MIN(widthScale, heightScale);
            
            self.scrollView.minimumZoomScale = minScale;
            self.scrollView.zoomScale = minScale;
            self.needsZoomScale = NO;
        }
    }
}

// ---------------------------------
//  Don't need this
//- (void)viewWillLayoutSubviews {
//  [super viewWillLayoutSubviews];
//  [self updateZoomScale];
//}
// ---------------------------------

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // ---------------------------------
    //  update zoom scale here
    [self updateZoomScale];
    // ---------------------------------
    
    [self recenterContent:self.scrollView];
}

// ---------------------------------
//  Don't need this
//- (void)viewDidAppear:(BOOL)animated {
//  [super viewDidAppear:animated];
//  [self recenterContent:self.scrollView];
//}
// ---------------------------------

#pragma mark - UIScrollViewDelegate

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

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    [self recenterContent:scrollView];
}

- (void)recenterContent:(UIScrollView *)scrollView {
    //this centers the content when it is smaller than the scrollView's bounds
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
    
    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}

@end

我是这样称呼它的:

MyImageViewController *vc = [MyImageViewController new];

UIImage *img = [UIImage imageNamed:@"bkg"];
if (nil == img) {
    NSLog(@"Could not load image!!!!");
    return;
}
[vc setImage:img];

[self.navigationController pushViewController:vc animated:YES];