【发布时间】:2021-07-28 01:28:01
【问题描述】:
我有一个 UIViewController,它的视图层次结构如下所示:
-
UIView-
UIScrollViewUIImageView
-
我有将图像视图定位在滚动视图框架中间的代码,如下所示:
- (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