【问题标题】:UIScrollView - when is contentSize setUIScrollView - contentSize 什么时候设置
【发布时间】:2021-07-28 01:28:01
【问题描述】:

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

  • UIView
    • UIScrollView
      • UIImageView

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

- (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

【问题讨论】:

  • 看看这个答案是否满足你的需要:stackoverflow.com/a/63469412/6257435
  • @DonMag 非常感谢。我会看看这个,看看。这看起来是一个非常棒的例子。
  • @DonMag 我看过你的例子,就居中代码而言,它们是相似的。除了在视图加载之前将图像居中之外,一切都对我有用。它现在的行为方式是它开始偏离中心,因为在显示视图之前 contentSize 为零,或者看起来如此。老实说,我不知道何时为使用 AutoLayout 的布局设置了 scrollView.contentSize。但是,如果我尝试在 viewDidAppear 中进行居中,那就太晚了,而且有跳跃。我用我的代码更新了我的问题。你看有什么不对吗?谢谢。

标签: ios uiviewcontroller uiscrollview uiimageview uiscrollviewdelegate


【解决方案1】:

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

我对您发布的代码进行了一些更改...请参阅 cmets(我将更改“包装”在

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

评论行:

@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];

【讨论】:

  • 非常感谢。那行得通。我的印象是图像视图会获得分配给它的图像的内在内容大小,但我猜不是?所以基本上,每次我在图像视图上设置图像时,我都应该设置宽度和高度。这是正确的外卖吗?非常感谢您对这个问题和我以前的问题的帮助。我真的很感激。
  • 获得基于.image的固有尺寸...这就是代码在第一次缩放之后起作用的原因.只是还没准备好。您也可以用[self.imageView sizeToFit]; 替换.frame = ... 行,它应该完成同样的事情。
  • 好的,很高兴知道。再次感谢您。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多