ScrollView& ImageView - 多个设备旋转后图像不居中

时间:2014-09-18 19:29:12

标签: ios uiscrollview autolayout

我的应用中有一个带有三个视图的segmentedControl,其中一个是scrollView,其工作方式类似于没有缩放的图库,pageControlimageView位于中心

层次结构就像

  

- >分段控制(3个视图):descriptionView,imageTabView,shareView
  ----> imagesTabView(UIView)
  ------>滚动视图
  ------> ImageView的
  ---->的PageControl

当设备是纵向或横向时,imageView图像会正确显示,它们会居中,滚动效果非常好。

唯一的问题是,当您再次转动设备时,如果图像是"在中间" (例如,是3的第2个或6个中的第3个),它被显示为偏心,远左或右,并且稍微滑动它返回中心,而如果图像是第一个或最后一个一,它运作正常。

我看过S.O.在各种主题上,尝试将contentView设置为scrollView的子视图,并将imageView添加为contentView的子视图,但没有成功,尝试将imageView附加到scrollView的底部或右侧,但也没有。

我觉得自己想要实现我想做的一步,唯一的问题就是我无法理解为什么它不会居中。

viewWillLayoutSubviews我已指定contentSize,以便在旋转时正确设置尺寸,例如

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.scrollView.contentSize = CGSizeMake (self.scrollView.frame.size.width * photosArray.count, 1);
}

以下是我如何初始化pageControl,scrollView和imageView:

-(void)configureImageTab{
    pageControl = [UIPageControl new];
    [pageControl addTarget:self action:@selector(changePage) forControlEvents:UIControlEventValueChanged];
    pageControl.translatesAutoresizingMaskIntoConstraints = NO;

    //Don't show pageControl when there are no photos
    if (photosURL.count == 0)
        pageControl.hidden = YES;

    //Configuring scrollView
    self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)];
    self.scrollView.pagingEnabled = YES;
    self.scrollView.delegate = self;
    self.scrollView.userInteractionEnabled = YES;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;

    //... Code cut - adding remote images to fetch to array

    //Actual setup -> scrollView adding imageView as subview with all the images
    for (int i =0; i< photosArray.count; i++){
        CGRect frame;
        frame.origin.x = self.scrollView.frame.size.width * i;
        frame.origin.y = 0;
        frame.size = self.scrollView.frame.size;

        //imageView setup
        imageView = [[UIImageView alloc]initWithFrame:frame];
        imageView.backgroundColor = [UIColor clearColor];
        imageView.clipsToBounds = YES;
        imageView.userInteractionEnabled = YES;
        imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
        imageView.translatesAutoresizingMaskIntoConstraints = YES;
        imageView.contentMode = UIViewContentModeScaleAspectFit;

        //Setting images urls
        [imageView setImageWithURL:[NSURL URLWithString:[photosArray objectAtIndex:i]] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        //Error handling 
            }
        }usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];

        //Adding gesture recognizer to scrollView and imageView as subview
        [self.scrollView addGestureRecognizer:singleTap];
        [self.scrollView addSubview:imageView];
    }

    //Setting the contentSize
    pageControl.numberOfPages = [photosURL count];

    [self.imageSegmentView addSubview:self.scrollView];
    [self.imageSegmentView addSubview:pageControl];

    //Constraints
    NSDictionary *views = @{@"pageControl" : pageControl, @"scrollView" : self.scrollView};

    [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]];
    [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]];
    [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:views]];

    [pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]];
}

#pragma mark - scrollView delegate -

-(void)scrollViewDidScroll:(UIScrollView *)sView{
    CGFloat pageWidth = self.scrollView.frame.size.width;
    int page = floor ((self.scrollView.contentOffset.x - pageWidth /2) /pageWidth) +1;
    self.pageControl.currentPage = page;
}

-(IBAction)changePage {
    CGRect frame;
    frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
    frame.origin.y = 0;
    frame.size = self.scrollView.frame.size;
    [self.scrollView scrollRectToVisible:frame animated:YES];
}

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    pageControlBeingUsed = NO;
}

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    pageControlBeingUsed = NO;
}

要做的一个注意事项:imageView正在使用autoresizingMask:没有它,它将无法正确显示图像。

我的猜测是,在scrollView委托中可能还有一些问题需要解决,但我不太确定。

任何建议都表示赞赏!

修改

我注意到在浏览用户的图片然后转动设备时,Twitter应用程序中也会出现同样的错误。

TL的

EDIT 2 ; DR

基本上,我们说我在水平scrollView中有3个图像,并带有分页功能。

我在第一张照片上将设备从纵向转为横向,并在其自己的位置显示,正确居中。

我移动到下一张照片,显示居中,然后我再次将设备转到肖像。照片未正确对齐,未居中

实际上,当设备多次旋转时,第一个和最后一个图像显示为居中。其他人没有集中

编辑3

我已经提取了一些行并制作了sample project来证明我遇到的问题。我猜这肯定与contentSize有关。

1 个答案:

答案 0 :(得分:4)

我们可以通过在界面即将旋转时记录当前页面来修复您正在讨论的特定错误(滚动视图在旋转后未与页面边界对齐),然后适当地设置滚动视图的contentOffset在旋转期间,系统更新了滚动视图的边界大小。让我们添加一个pageNumberPriorToRotation实例变量:

@implementation ViewController {
    CGFloat pageNumberPriorToRotation;
}

然后,我们在接口即将旋转时设置它:

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [self setPageNumberPriorToRotation];
}

- (void)setPageNumberPriorToRotation {
    CGRect bounds = self.scrollView.bounds;
    static const int kNumberOfImages = 3;
    pageNumberPriorToRotation = fmin(round(bounds.origin.x / bounds.size.width),
        kNumberOfImages - 1);
}

我们用它在界面旋转期间设置滚动视图的contentOffset

-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [self updateScrollViewLayout];
}

- (void)updateScrollViewLayout {
    CGRect bounds = self.scrollView.bounds;
    bounds.origin.x = bounds.size.width * pageNumberPriorToRotation;
    self.scrollView.bounds = bounds;
}

这会照顾您的主要投诉:滚动视图在轮换后将始终与页面视图边界对齐。

然而...

滚动视图交互还存在一些其他问题。在横向方向,我无法滚动到第三个图像。旋转到横向并回到肖像后,我可以滚动到空白的第四页。这些问题大概就是你所说的“内容大小确实存在的问题”。

此外,您的代码存在许多问题。它使用一些过时的样式,比如为属性显式声明实例变量并将实例变量放在头文件中。它也受Massive View Controller的影响。它真的可以用现代风格重写,并使用UITabBarControllerUIPageViewController等功能。

无论如何,你可能既没有时间也没有倾向于做那么多的工作,所以我会告诉你如何解决contentSize问题并同时减少你的VC。

我将创建一个名为UIScrollView的{​​{1}}子类。你给我一系列的图像,我将负责设置其子视图并在旋转后对齐页面边界。这是我的头文件:

ImageScrollView

ImageScrollView.h

要实现这一点,我需要一些实例变量:

#import <UIKit/UIKit.h> @interface ImageScrollView : UIScrollView @property (nonatomic, copy) NSArray *images; @end

ImageScrollView.m

无论如何,首先我将实现公共API,它只是#import "ImageScrollView.h" #import <tgmath.h> @implementation ImageScrollView { NSMutableArray *imageSubviews; CGSize priorSize; CGFloat pageNumber; BOOL needsToSyncSubviewsWithImages : 1; } 属性:

images

请注意,当您设置#pragma mark - Public API @synthesize images = _images; - (void)setImages:(NSArray *)images { _images = [images copy]; needsToSyncSubviewsWithImages = YES; } 数组时,我会立即创建子视图。现在,我只设置images标志,以便在布局阶段我知道这样做。

needsToSyncSubviewsWithImages

接下来,我需要覆盖#pragma mark - UIView overrides ,这样我就可以在布局阶段完成真正的工作。如果我的layoutSubviews数组已更改,或者我的layoutSubviews已更改,系统会在布局阶段向我发送subviews

因为我是滚动视图,并且因为滚动视图的bounds实际上只是其contentOffset的别名,系统会向我发送bounds.origin很多:每次滚动查看滚动。所以我要小心,只在layoutSubviews中做必要的工作。

layoutSubviews

我要做的第一件事是调用- (void)layoutSubviews { ,它会让我们自动布局工作(如果你正在使用它)并更新我的滚动指示器(如果它们可见)。

super

接下来,如果我有新图像,我会设置显示它们的子视图。

    [super layoutSubviews];

接下来,如果我设置了新的子视图,或者我已经更改了尺寸,我会为新尺寸布置我的子视图框架,并与页面边界对齐。

    if (needsToSyncSubviewsWithImages) {
        [self syncSubviewsWithImages];
    }

最后,我更新了我的状态。

    if (needsToSyncSubviewsWithImages || !CGSizeEqualToSize(self.bounds.size, priorSize)) {
        [self layoutForNewSize];
    }

当然,我将所有实际工作委托给了辅助方法,所以现在我需要实现它们。

    needsToSyncSubviewsWithImages = NO;
    priorSize = self.bounds.size;
    [self updatePageNumber];
}

要将我的子视图与我的图像同步,我需要做三件事。我需要确保我实际上已经分配了我的#pragma mark - Implementation details 数组,我需要确保每个图像都在子视图中,并且我需要确保我没有任何额外的图像子视图(以防我的{ {1}}数组缩小了。)

imageSubviews

当我想获取索引的图像视图时,我可能会发现我还没有真正创建足够的子视图,所以我按需创建它们:

images

请注意,我已将- (void)syncSubviewsWithImages { [self ensureImageSubviewsArrayExists]; [self putImagesInSubviews]; [self removeExtraSubviews]; } - (void)ensureImageSubviewsArrayExists { if (imageSubviews == nil) { imageSubviews = [NSMutableArray arrayWithCapacity:self.images.count]; } } - (void)putImagesInSubviews { [self.images enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop) { [self putImage:obj inSubviewAtIndex:i]; }]; } - (void)removeExtraSubviews { while (imageSubviews.count > self.images.count) { [imageSubviews.lastObject removeFromSuperview]; [imageSubviews removeLastObject]; } } - (void)putImage:(UIImage *)image inSubviewAtIndex:(NSUInteger)i { UIImageView *imageView = [self imageViewAtIndex:i]; imageView.image = image; } 设置为自动调整不会实际修改我的子视图帧。相反,我会“手动”将它们列出来。

好的,现在我需要实现设置子视图框架的方法,并在我的尺寸发生变化时对齐页面边界。

- (UIImageView *)imageViewAtIndex:(NSUInteger)i {
    while (i >= imageSubviews.count) {
        UIView *view = [[UIImageView alloc] init];
        view.contentMode = UIViewContentModeScaleAspectFit;
        view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
        [self addSubview:view];
        [imageSubviews addObject:view];
    }
    return imageSubviews[i];
}

设置子视图帧需要循环遍历它们,从左到右布置它们。在我列出最后一个之后,我知道我的autoresizingMask。请注意,我只需要遍历- (void)layoutForNewSize { [self setSubviewFramesAndContentSize]; [self alignToNearestPage]; } ,而不是contentSize,因为imageSubviews还包含滚动指示符。

self.subviews

为了与最近的页面对齐,我根据最后的已知页码和新的尺寸设置self.subviews

- (void)setSubviewFramesAndContentSize {
    CGRect frame = self.bounds;
    frame.origin = CGPointZero;
    for (UIView *subview in imageSubviews) {
        subview.frame = frame;
        frame.origin.x += frame.size.width;
    }
    self.contentSize = CGSizeMake(frame.origin.x, frame.size.height);
}

最后,每次滚动时我都需要更新我的页码,所以我会在轮换的情况下更新页码:

contentOffset

现在,您可以更新- (void)alignToNearestPage { self.contentOffset = CGPointMake(pageNumber * self.bounds.size.width, 0); } 以使用- (void)updatePageNumber { // Note that self.contentOffset == self.bounds.origin. CGRect bounds = self.bounds; pageNumber = fmin(round(bounds.origin.x / bounds.size.width), self.images.count - 1); } @end 。这主要涉及剥离东西:

ViewController

您还需要在头文件中将声明的ImageScrollView类型更改为-(void)configureImageTab{ //Page control pageControl = [UIPageControl new]; pageControl.currentPageIndicatorTintColor = [UIColor blackColor]; pageControl.pageIndicatorTintColor = [UIColor grayColor]; [pageControl addTarget:self action:@selector(changePage) forControlEvents:UIControlEventValueChanged]; pageControl.translatesAutoresizingMaskIntoConstraints = NO; //Configuring scrollView self.scrollView = [[ImageScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)]; self.scrollView.backgroundColor = [UIColor clearColor]; self.scrollView.pagingEnabled = YES; self.scrollView.delegate = self; self.scrollView.userInteractionEnabled = YES; self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; //Adding imageURLS to array photos = @[ [UIImage imageNamed:@"createBootableUSBInstallDrive1"], [UIImage imageNamed:@"createBootableUSBInstallDrive2"], [UIImage imageNamed:@"createBootableUSBInstallDrive3"]]; self.scrollView.images = photos; pageControl.numberOfPages = [photos count]; [self.imageSegmentView addSubview:self.scrollView]; [self.imageSegmentView addSubview:pageControl]; NSDictionary *views = @{@"pageControl" : pageControl, @"scrollView" : self.scrollView}; [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]]; [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:nil views:views]]; [self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]]; [pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]]; } 。您可以完全取消scrollViewImageScrollViewviewWillLayoutSubviews方法。

我已将修改后的测试项目版本上传到this github repository