我的应用程序中有一个有三个视图的segmentedControl,其中一个是scrollView,它的工作方式就像没有缩放的画廊,中心是pageControl和imageView。
层次结构就像
->分段控制(3个视图):descriptionView、imageTabView、shareView -> imagesTabView (UIView) - scrollView - 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应用程序中,当浏览用户的图片,然后打开设备时,也会出现同样的错误。
编辑2用于TL;DR
基本上,假设我在带有分页的水平scrollView中有3幅图像。
在第一张照片上,我把设备从肖像转到风景,并在它自己的位置显示,正确的居中。
我移到下一张照片,显示居中,然后我再把设备转到肖像。照片没有正确对齐,也没有居中。
实际上,当设备旋转多次时,第一张和最后一幅图像都以中心显示。其他的都不是中心
编辑3
我提取了一些行,并制作了一个样本工程来演示我遇到的问题。我想contentSize肯定出了点问题。
发布于 2014-09-25 17:18:15
我们可以通过在界面即将旋转时记录当前页面,然后在系统更新滚动视图的边界大小之后,在旋转期间适当地设置滚动视图的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;
}这就解决了您的主要抱怨:滚动视图在旋转后将始终对齐到页面视图边界。
然而,…
滚动视图交互还有其他一些问题。在景观定位中,我不能滚动到第三张图片。在旋转到风景,回到肖像,我可以滚动到空白的第四页。这些问题大概就是你所说的“contentSize肯定有什么问题”。
此外,您的代码还存在一些问题。它使用了一些过时的样式,比如显式声明属性的实例变量,并将实例变量放在头文件中。它还受到大视场控制器的影响。它确实可以用现代风格重写,并使用像UITabBarController和UIPageViewController这样的特性。
不管怎么说,你可能既没有时间也没有兴趣做那么多的工作,所以我将告诉你如何解决contentSize的问题,同时瘦身你的VC。
我将创建一个名为UIScrollView的ImageScrollView子类。您给我图像数组,我将负责设置它的子视图,并在旋转后对齐到页面边界。以下是我的头文件:
ImageScrollView.h
#import <UIKit/UIKit.h>
@interface ImageScrollView : UIScrollView
@property (nonatomic, copy) NSArray *images;
@end要实现这一点,我需要一些实例变量:
ImageScrollView.m
#import "ImageScrollView.h"
#import <tgmath.h>
@implementation ImageScrollView {
NSMutableArray *imageSubviews;
CGSize priorSize;
CGFloat pageNumber;
BOOL needsToSyncSubviewsWithImages : 1;
}无论如何,首先我将实现公共API,它只是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数组改变了,或者如果我的bounds改变了,系统会在布局阶段向我发送subviews。
因为我是滚动视图,而且因为滚动视图的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];
}当然,我将所有真正的工作委托给了helper方法,所以现在我需要实现这些方法。
#pragma mark - Implementation details为了使我的子视图与我的图像同步,我需要做三件事。我需要确保我实际上已经分配了我的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]];
}还需要将头文件中声明的scrollView类型更改为ImageScrollView。您可以完全消除viewWillLayoutSubviews、willRotateToInterfaceOrientation:duration:和willAnimateRotationToInterfaceOrientation:duration:方法。
我已经将您测试项目的修改版本上传到这个github存储库。
https://stackoverflow.com/questions/25920880
复制相似问题