首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ScrollView & ImageView -多个设备旋转后的图像不居中

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

Stack Overflow用户
提问于 2014-09-18 19:29:12
回答 1查看 1.4K关注 0票数 2

我的应用程序中有一个有三个视图的segmentedControl,其中一个是scrollView,它的工作方式就像没有缩放的画廊,中心是pageControlimageView

层次结构就像

->分段控制(3个视图):descriptionView、imageTabView、shareView -> imagesTabView (UIView) - scrollView - imageView -> pageControl

当该设备是肖像或景观,imageView图像显示正确,他们的中心和滚动工作非常好。

唯一的问题是,当你再次转动设备时,如果图像是“中间的”(例如,3的第2或6的第3),它会被显示为偏转、极左或右,并且稍微滑动一下它就会回到中心,而如果图像是第一个或最后一个,它会正常工作。

我在这里查看了不同线程上的S.O.,尝试将contentView设置为scrollView的子视图,并将imageView添加为contentView的子视图,但没有工作,试图将imageView附加到scrollView的底部或右侧,但也没有工作。

我觉得我离实现我想做的事情还有一步之遥,唯一的问题是我无法理解为什么它不是中心的。

viewWillLayoutSubviews中,我指定了contentSize,以便当它旋转时,它的大小被正确设置,如下

代码语言:javascript
复制
-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.scrollView.contentSize = CGSizeMake (self.scrollView.frame.size.width * photosArray.count, 1);
}

下面是如何初始化pageControl、scrollView和imageView:

代码语言:javascript
复制
-(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应用程序中,当浏览用户的图片,然后打开设备时,也会出现同样的错误。

编辑2用于TL;DR

基本上,假设我在带有分页的水平scrollView中有3幅图像。

在第一张照片上,我把设备从肖像转到风景,并在它自己的位置显示,正确的居中。

我移到下一张照片,显示居中,然后我再把设备转到肖像。照片没有正确对齐,也没有居中。

实际上,当设备旋转多次时,第一张和最后一幅图像都以中心显示。其他的都不是中心

编辑3

我提取了一些行,并制作了一个样本工程来演示我遇到的问题。我想contentSize肯定出了点问题。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-09-25 17:18:15

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

代码语言:javascript
复制
@implementation ViewController {
    CGFloat pageNumberPriorToRotation;
}

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

代码语言:javascript
复制
- (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

代码语言:javascript
复制
-(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;
}

这就解决了您的主要抱怨:滚动视图在旋转后将始终对齐到页面视图边界。

然而,…

滚动视图交互还有其他一些问题。在景观定位中,我不能滚动到第三张图片。在旋转到风景,回到肖像,我可以滚动到空白的第四页。这些问题大概就是你所说的“contentSize肯定有什么问题”。

此外,您的代码还存在一些问题。它使用了一些过时的样式,比如显式声明属性的实例变量,并将实例变量放在头文件中。它还受到大视场控制器的影响。它确实可以用现代风格重写,并使用像UITabBarControllerUIPageViewController这样的特性。

不管怎么说,你可能既没有时间也没有兴趣做那么多的工作,所以我将告诉你如何解决contentSize的问题,同时瘦身你的VC。

我将创建一个名为UIScrollViewImageScrollView子类。您给我图像数组,我将负责设置它的子视图,并在旋转后对齐到页面边界。以下是我的头文件:

ImageScrollView.h

代码语言:javascript
复制
#import <UIKit/UIKit.h>

@interface ImageScrollView : UIScrollView

@property (nonatomic, copy) NSArray *images;

@end

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

ImageScrollView.m

代码语言:javascript
复制
#import "ImageScrollView.h"
#import <tgmath.h>

@implementation ImageScrollView {
    NSMutableArray *imageSubviews;
    CGSize priorSize;
    CGFloat pageNumber;
    BOOL needsToSyncSubviewsWithImages : 1;
}

无论如何,首先我将实现公共API,它只是images属性:

代码语言:javascript
复制
#pragma mark - Public API

@synthesize images = _images;

- (void)setImages:(NSArray *)images {
    _images = [images copy];
    needsToSyncSubviewsWithImages = YES;
}

注意,当您设置images数组时,我不立即创建子视图。现在,我只是设置了needsToSyncSubviewsWithImages标志,这样我就知道如何在布局阶段做到这一点。

代码语言:javascript
复制
#pragma mark - UIView overrides

接下来,我需要覆盖layoutSubviews,以便在布局阶段完成真正的工作。如果我的layoutSubviews数组改变了,或者如果我的bounds改变了,系统会在布局阶段向我发送subviews

因为我是滚动视图,而且因为滚动视图的contentOffset实际上只是它的bounds.origin的别名,所以系统给我发送了很多layoutSubviews:每次滚动视图滚动。因此,我想谨慎地在layoutSubviews中只做必要的工作。

代码语言:javascript
复制
- (void)layoutSubviews {

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

代码语言:javascript
复制
    [super layoutSubviews];

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

代码语言:javascript
复制
    if (needsToSyncSubviewsWithImages) {
        [self syncSubviewsWithImages];
    }

接下来,如果设置了新的子视图,或者更改了大小,则为新大小列出子视图的框架,并与页面边界对齐。

代码语言:javascript
复制
    if (needsToSyncSubviewsWithImages || !CGSizeEqualToSize(self.bounds.size, priorSize)) {
        [self layoutForNewSize];
    }

最后,我更新我的状态。

代码语言:javascript
复制
    needsToSyncSubviewsWithImages = NO;
    priorSize = self.bounds.size;
    [self updatePageNumber];
}

当然,我将所有真正的工作委托给了helper方法,所以现在我需要实现这些方法。

代码语言:javascript
复制
#pragma mark - Implementation details

为了使我的子视图与我的图像同步,我需要做三件事。我需要确保我实际上已经分配了我的imageSubviews数组,我需要确保每个图像都在一个子视图中,并且我需要确保我没有任何额外的图像子视图(以防我的images数组变得更小)。

代码语言:javascript
复制
- (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;
}

当我想获得索引的图像视图时,我可能会发现我还没有创建足够的子视图,所以我会根据需要创建它们:

代码语言:javascript
复制
- (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,这样自动调整大小实际上不会修改我的子视图框架。相反,我会“手动”列出它们。

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

代码语言:javascript
复制
- (void)layoutForNewSize {
    [self setSubviewFramesAndContentSize];
    [self alignToNearestPage];
}

设置子视图框架需要对它们进行循环,将它们从左到右放置。在我列出最后一个之后,我知道我的contentSize了。请注意,我只需要在imageSubviews上循环,而不是self.subviews,因为self.subviews也包含滚动指示器。

代码语言:javascript
复制
- (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

代码语言:javascript
复制
- (void)alignToNearestPage {
    self.contentOffset = CGPointMake(pageNumber * self.bounds.size.width, 0);
}

最后,每次滚动时我都需要更新我的页码,所以我将在轮转时使用它:

代码语言:javascript
复制
- (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。这主要涉及到撕掉一些东西:

代码语言:javascript
复制
-(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]];
}

还需要将头文件中声明的scrollView类型更改为ImageScrollView。您可以完全消除viewWillLayoutSubviewswillRotateToInterfaceOrientation:duration:willAnimateRotationToInterfaceOrientation:duration:方法。

我已经将您测试项目的修改版本上传到这个github存储库

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25920880

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档