ios自定义userInterface旋转

时间:2013-02-26 12:08:33

标签: ios uiviewcontroller containers

我正在开发一个容器UIViewController。 我希望容器委托轮换的管理控制器。

// this is in every controller by extending uiviewcontroller with a category
- (BOOL)shouldAutorotate {
    UIInterfaceOrientation orientation = [[UIDevice currentDevice] orientation];
    return [self shouldAutorotateToInterfaceOrientation:orientation];
}

// this is only in the root container because it embed the entire view hierarchy
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
    return [self.currentController shouldAutorotate];
}

到目前为止一切顺利。

现在我希望容器始终保持纵向状态并旋转其contentView来控制旋转,然后修补这样的方法:

// this is only in the root container because it embed the entire view hierarchy
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
        // forward the message to the current controller for automatic behavior
    [self.currentController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

// this is only in the root container because it embed the entire view hierarchy
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
        // forward the message to the current controller for automatic behavior
    [self.currentController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

// this is only in the root container because it embed the entire view hierarchy
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
    {
    BOOL supportedOrientation = [self.currentController shouldAutorotate];

    if (supportedOrientation && self.currentOrientation != toInterfaceOrientation)
    {
        // virtual orientation by rotating the content view
        CGRect frame = self.contentView.bounds;

        CGPoint origin = self.contentView.center;

        float rotation = [self checkRotationForOrientation:toInterfaceOrientation];

        float w = frame.size.width;
        float h = frame.size.height;

        // check the right height and width for the new orientation
        if (UIInterfaceOrientationIsPortrait(toInterfaceOrientation)) {
            frame.size = CGSizeMake(MIN(w, h), MAX(w, h));
        } else {
            frame.size = CGSizeMake(MAX(w, h), MIN(w, h));
        }

        // manually call willRotateEtc and willAnimateEtc because the rotation is virtual
        [self.currentController willRotateToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];
        [self.currentController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];

        // animate the rotation
        [UIView animateWithDuration:kAnimationDuration animations:^{
            self.contentView.transform = CGAffineTransformMakeRotation(rotation);
            self.contentView.bounds = frame;
            self.contentView.center = origin;
        }];

        // update the new virtual orientation for the controller and the container
        self.currentController.currentOrientation = toInterfaceOrientation;
        self.currentOrientation = toInterfaceOrientation;
    }

    return NO;
}

currentController是PhotoViewController的一个实例(带有一些修改的Apple示例PhotoScroller的旧样式),它是:

/*
 File: PhotoViewController.h
 Abstract: Configures and displays the paging scroll view and handles tiling and page configuration.

 */

#import <UIKit/UIKit.h>
#import "ImageScrollView.h"
#import "CRWCacheProtocol.h"

@protocol PhotoViewControllerDelegate;

@interface PhotoViewController : UIViewController <UIScrollViewDelegate, ImageScrollViewDelegate, CRWCacheProtocol> {
    NSMutableSet *recycledPages;
    NSMutableSet *visiblePages;

// these values are stored off before we start rotation so we adjust our content offset appropriately during rotation
int           firstVisiblePageIndexBeforeRotation;
CGFloat       percentScrolledIntoFirstVisiblePage;
}

@property (unsafe_unretained, nonatomic) IBOutlet UIScrollView *pagingScrollView;
@property (unsafe_unretained, nonatomic) IBOutlet UILabel *pageLabel;
@property (retain, nonatomic) NSString *dataFileName;

@property (assign, nonatomic) NSInteger currentPage;

@property (unsafe_unretained, nonatomic) id<PhotoViewControllerDelegate> photoViewControllerDelegate;

- (NSArray *)imageData;
- (void) setImageData:(NSArray *) customImageData;

- (void)configurePage:(ImageScrollView *)page forIndex:(NSUInteger)index;
- (BOOL)isDisplayingPageForIndex:(NSUInteger)index;

- (CGRect)frameForPagingScrollView;
- (CGRect)frameForPageAtIndex:(NSUInteger)index;
- (CGSize)contentSizeForPagingScrollView;

- (void)tilePages;
- (ImageScrollView *)dequeueRecycledPage;

- (NSUInteger)imageCount;
- (NSString *)imageNameAtIndex:(NSUInteger)index;
- (CGSize)imageSizeAtIndex:(NSUInteger)index;
- (UIImage *)imageAtIndex:(NSUInteger)index;

@end

@protocol PhotoViewControllerDelegate <NSObject>

@optional
- (void) photoViewController:(PhotoViewController *) controller willDisplayPhoto:(ImageScrollView *) photo;
- (void) photoViewController:(PhotoViewController *) controller didDisplayPhoto:(ImageScrollView *) photo;

@end

和“.m”

/*
     File: PhotoViewController.m
 Abstract: Configures and displays the paging scroll view and handles tiling and page configuration.

 */

#import "PhotoViewController.h"
#import "CRWCache.h"
#import "CRWebKit.h"

@interface PhotoViewController ()

@property (nonatomic,retain) NSArray *customImageData;
@property (nonatomic,assign) NSInteger indexForDownloadingImage;

@end

@implementation PhotoViewController

- (void) scrollToStartPage
{
    float pageWidth = self.pagingScrollView.contentSize.width / [self imageCount];
    float pageHeight = self.pagingScrollView.contentSize.height;

    CGRect frame = CGRectMake(pageWidth*self.currentPage, 0, pageWidth, pageHeight);
    [self.pagingScrollView scrollRectToVisible:frame animated:NO];
}

- (ImageScrollView *) displayedPageForIndex:(NSUInteger)index
{
    ImageScrollView *page = nil;
    for (ImageScrollView *currentPage in visiblePages) {
        if (currentPage.index == index) {
            page = currentPage;
            break;
        }
    }
    return page;
}

#pragma mark -
#pragma mark View loading and unloading

-(void)viewDidAppear:(BOOL)animated
{
    self.pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
    [self scrollToStartPage];
    [self tilePages];
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Step 1: make the outer paging scroll view
    self.pagingScrollView.pagingEnabled = YES;
    //self.pagingScrollView.backgroundColor = [UIColor blackColor];
    self.pagingScrollView.backgroundColor = [UIColor blackColor];
    self.pagingScrollView.showsVerticalScrollIndicator = NO;
    self.pagingScrollView.showsHorizontalScrollIndicator = NO;
    self.pagingScrollView.delegate = self;

    // Step 2: prepare to tile content
    recycledPages = [[NSMutableSet alloc] init];
    visiblePages  = [[NSMutableSet alloc] init];
}

- (void)viewDidUnload
{
    [self setDataFileName:nil];
    [self setPageLabel:nil];
    [self setCustomImageData:nil];
    [super viewDidUnload];
    self.pagingScrollView = nil;
    recycledPages = nil;
    visiblePages = nil;
}

#pragma mark -
#pragma mark Tiling and page configuration

- (void)tilePages
{
    // Calculate which pages are visible
    CGRect visibleBounds = self.pagingScrollView.bounds;
    int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
    int lastNeededPageIndex  = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
    firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
    lastNeededPageIndex  = MIN(lastNeededPageIndex, [self imageCount] - 1);

    // Recycle no-longer-visible pages 
    for (ImageScrollView *page in visiblePages) {
        if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
            [recycledPages addObject:page];
            [page removeFromSuperview];
        }
    }
    [visiblePages minusSet:recycledPages];

    // add missing pages
    for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {

        // imposta il contatore di pagine e la pagina corrente
        self.pageLabel.text = [NSString stringWithFormat:@"%i/%i", index + 1, [self imageCount]];
        self.currentPage = index;

        ImageScrollView *page = [self displayedPageForIndex:index];
        if (page == nil) {
            page = [self dequeueRecycledPage];
            if (page == nil) {
                page = [[ImageScrollView alloc] init];
            }
            [self configurePage:page forIndex:index];

            page.imageScrollViewDelegate = self;

            // informo il delegate che sto per visualizzare l'immagine
            if ([self.photoViewControllerDelegate conformsToProtocol:@protocol(PhotoViewControllerDelegate)] &&
                [self.photoViewControllerDelegate respondsToSelector:@selector(photoViewController:willDisplayPhoto:)]) {
                [self.photoViewControllerDelegate photoViewController:self willDisplayPhoto:page];
            }

            [self.pagingScrollView addSubview:page];

            [visiblePages addObject:page];

            // informo il delegate che ho visualizzato l'immagine
            if ([self.photoViewControllerDelegate conformsToProtocol:@protocol(PhotoViewControllerDelegate)] &&
                [self.photoViewControllerDelegate respondsToSelector:@selector(photoViewController:didDisplayPhoto:)]) {
                [self.photoViewControllerDelegate photoViewController:self didDisplayPhoto:page];
            }
        }
    }    
}

- (ImageScrollView *)dequeueRecycledPage
{
    ImageScrollView *page = [recycledPages anyObject];
    if (page) {
        [recycledPages removeObject:page];
    }
    return page;
}

- (BOOL)isDisplayingPageForIndex:(NSUInteger)index
{
    BOOL foundPage = NO;
    for (ImageScrollView *page in visiblePages) {
        if (page.index == index) {
            foundPage = YES;
            break;
        }
    }
    return foundPage;
}

- (void)configurePage:(ImageScrollView *)page forIndex:(NSUInteger)index
{
    page.index = index;
    page.frame = [self frameForPageAtIndex:index];
    /*
    // Use tiled images
    [page displayTiledImageNamed:[self imageNameAtIndex:index] size:[self imageSizeAtIndex:index]];
    /*/
    // To use full images instead of tiled images, replace the "displayTiledImageNamed:" call
    // above by the following line:
    [page displayImage:[self imageAtIndex:index]];
    //*/
}


#pragma mark -
#pragma mark ScrollView delegate methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    [self tilePages];
}

#pragma mark -
#pragma mark View controller rotation methods
/*
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 
{
    return YES;
}
*/
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    // here, our pagingScrollView bounds have not yet been updated for the new interface orientation. So this is a good
    // place to calculate the content offset that we will need in the new orientation
    CGFloat offset = self.pagingScrollView.contentOffset.x;
    CGFloat pageWidth = self.pagingScrollView.bounds.size.width;

    if (offset >= 0) {
        firstVisiblePageIndexBeforeRotation = floorf(offset / pageWidth);
        percentScrolledIntoFirstVisiblePage = (offset - (firstVisiblePageIndexBeforeRotation * pageWidth)) / pageWidth;
    } else {
        firstVisiblePageIndexBeforeRotation = 0;
        percentScrolledIntoFirstVisiblePage = offset / pageWidth;
    }    
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    // recalculate contentSize based on current orientation
    self.pagingScrollView.contentSize = [self contentSizeForPagingScrollView];

    // adjust frames and configuration of each visible page
    for (ImageScrollView *page in visiblePages) {
        CGPoint restorePoint = [page pointToCenterAfterRotation];
        CGFloat restoreScale = [page scaleToRestoreAfterRotation];
        page.frame = [self frameForPageAtIndex:page.index];
        [page setMaxMinZoomScalesForCurrentBounds];
        [page restoreCenterPoint:restorePoint scale:restoreScale];

    }

    // adjust contentOffset to preserve page location based on values collected prior to location
    CGFloat pageWidth = self.pagingScrollView.bounds.size.width;
    CGFloat newOffset = (firstVisiblePageIndexBeforeRotation * pageWidth) + (percentScrolledIntoFirstVisiblePage * pageWidth);
    self.pagingScrollView.contentOffset = CGPointMake(newOffset, 0);
}

#pragma mark -
#pragma mark  Frame calculations
#define PADDING  10

- (CGRect)frameForPagingScrollView {
    CGRect frame = [[UIScreen mainScreen] bounds];
    frame.origin.x -= PADDING;
    frame.size.width += (2 * PADDING);
    return frame;
}

- (CGRect)frameForPageAtIndex:(NSUInteger)index {
    // We have to use our paging scroll view's bounds, not frame, to calculate the page placement. When the device is in
    // landscape orientation, the frame will still be in portrait because the pagingScrollView is the root view controller's
    // view, so its frame is in window coordinate space, which is never rotated. Its bounds, however, will be in landscape
    // because it has a rotation transform applied.
    CGRect bounds = self.pagingScrollView.bounds;
    CGRect pageFrame = bounds;
    pageFrame.size.width -= (2 * PADDING);
    pageFrame.origin.x = (bounds.size.width * index) + PADDING;
    return pageFrame;
}

- (CGSize)contentSizeForPagingScrollView {
    // We have to use the paging scroll view's bounds to calculate the contentSize, for the same reason outlined above.
    CGRect bounds = self.pagingScrollView.bounds;
    return CGSizeMake(bounds.size.width * [self imageCount], bounds.size.height);
}


#pragma mark -
#pragma mark Image wrangling

- (void)setImageData:(NSArray *)customImageData
{
    _customImageData = customImageData;
}

- (NSArray *)imageData {
    static NSArray *__imageData = nil; // only load the imageData array once
    if (self.customImageData == nil) {
        // read the filenames/sizes out of a plist in the app bundle
//        NSString *path = [[NSBundle mainBundle] pathForResource:@"ImageData" ofType:@"plist"];
        NSString *path = [[NSBundle mainBundle] pathForResource:self.dataFileName ofType:@"plist"];
        NSData *plistData = [NSData dataWithContentsOfFile:path];
        NSString *error; NSPropertyListFormat format;
        __imageData = [NSPropertyListSerialization propertyListFromData:plistData
                                                        mutabilityOption:NSPropertyListImmutable
                                                                  format:&format
                                                        errorDescription:&error];
        if (!__imageData) {
            NSLog(@"Failed to read image names. Error: %@", error);
        }
    }
    else if (self.customImageData != nil)
    {
        __imageData = self.customImageData;
    }
    return __imageData;
}

- (UIImage *)imageAtIndex:(NSUInteger)index {
    // use "imageWithContentsOfFile:" instead of "imageNamed:" here to avoid caching our images
    NSString *imageName = [self imageNameAtIndex:index];

    UIImage *image;
    if ([imageName rangeOfString:@"http://"].location != NSNotFound) {
        NSURL *url = [NSURL URLWithString:imageName];
        //*

        NSString *cachedImage = [CRWCache cacheFileFromURL:url waitUntilFinish:NO andDelegate:self];

        if ([CRWCache isExpiredURL:url] || ![[NSFileManager defaultManager] fileExistsAtPath:cachedImage]) {
            self.indexForDownloadingImage = index;
        } else {
            self.indexForDownloadingImage = -1;
        }

        NSLog(@"cached image file = %@", cachedImage);
        if (self.indexForDownloadingImage < 0) {
            image = [UIImage imageWithContentsOfFile:cachedImage];
        } else {
            image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:kPhotoViewControllerLoadingImage]];
        }
        /*/
        NSData *data = [NSData dataWithContentsOfURL:url];
        image = [UIImage imageWithData:data];
        //*/
    }
    else {
        NSString *path = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:imageName];
        NSLog(@"image name = %@", path);
        image = [UIImage imageWithContentsOfFile:path];
    }

    return  image;
}

- (NSString *)imageNameAtIndex:(NSUInteger)index {
    NSString *name = nil;
    if (index < [self imageCount]) {
        NSDictionary *data = [[self imageData] objectAtIndex:index];
        name = [data valueForKey:kPhotoViewControllerImageName];
    }
    return name;
}

- (CGSize)imageSizeAtIndex:(NSUInteger)index {
    CGSize size = CGSizeZero;
    if (index < [self imageCount]) {
        NSDictionary *data = [[self imageData] objectAtIndex:index];
        size.width = [[data valueForKey:@"width"] floatValue];
        size.height = [[data valueForKey:@"height"] floatValue];
    }
    return size;
}

- (NSUInteger)imageCount {
    /*
    static NSUInteger __count = NSNotFound;  // only count the images once
    if (__count == NSNotFound) {
        __count = [[self imageData] count];
    }
    return __count;
    */
    return [[self imageData] count];
}

#pragma mark - ImageScrollViewDelegate

- (void)imageScrollViewRecivedTouch:(ImageScrollView *)view
{

}

#pragma mark - CRWCacheProtocol

- (void)finischCacheingWithPath:(NSString *)downloadedFilePath
{
    ImageScrollView *page = [self displayedPageForIndex:self.indexForDownloadingImage];
    if (page != nil)
    {
        [page removeFromSuperview];
        [recycledPages addObject:page];
        [visiblePages minusSet:recycledPages];
    }
    [self tilePages];
}

- (void)failedCacheingWithPath:(NSString *)downloadedFilePath
{
    NSLog(@"FAILED for path: %@",downloadedFilePath);
}

@end

好的,现在问题了。当我以模态方式介绍PhotoViewController时,旋转是正确的,图片显示正确的位置。

然而,当我在容器中呈现PhotoViewController(以及其contentView)时,所有子视图都被正确更改,除了保留在其原始方向(纵向)中的图像和在scrollview中“浮动”的图像。经过一些调试后我发现这是因为方法

- (CGRect) frameForPageAtIndex: (NSUInteger) index {...}

通过

调用
- (void) willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration {...}

依次由

手动调用
  - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) {...} toInterfaceOrientation container

没有接收到旋转的pagingScrollView但总是接收纵向方向。这似乎是正确的,因为那时还没有应用旋转。显然,使用模态转换,一切正常,但由于各种原因,我需要容器来管理旋转。

有没有办法将旋转传播到pagingScrollView,还是有另一种让容器控制旋转的方法?

编辑:一个有效但不是最好的小解决方法......以这种方式纠正方法

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
        ...

        // manually call willRotateEtc because the rotation is virtual
        [self.currentController willRotateToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];

        // animate the rotation
        [UIView animateWithDuration:kAnimationDuration animations:^{
            self.contentView.transform = CGAffineTransformMakeRotation(rotation);
            self.contentView.bounds = frame;
            self.contentView.center = origin;
        }];

        // manually call willAnimateEtc because the rotation is virtual
        [self.currentController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];

        ...
}

我正确地调整了pagingScrollView的大小。旋转效果很好,但是动画会在第二个,第三个......等图像上产生一个拉链效果,因为它在旋转后居中,而不是在旋转执行时。

1 个答案:

答案 0 :(得分:0)

最后得到了更好的结果:)

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
  {
    ...

    // manually call willRotateEtc because the rotation is virtual
    [self.currentController willRotateToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];

    // animate the rotation
    [UIView animateWithDuration:kAnimationDuration animations:^{
        self.contentView.transform = CGAffineTransformMakeRotation(rotation);
        self.contentView.bounds = frame;
        self.contentView.center = origin;
    // manually call willAnimateEtc because the rotation is virtual
    [self.currentController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:kAnimationDuration];
    }];

    ...
}

由于图像在动画之后而不是在动画期间旋转,简单地说,我将调用移动到动画块内的willAnimateEtc。通过这种方式,我获得的旋转非常类似于操作系统自动执行的旋转。唯一仍然存在的是:

1)在图像右侧略微轻弹,但这是完全可以接受的

2)状态栏以纵向方式定位,这不是很好,但现在还可以

我打开这个问题,这样任何人都可以轻松地写一个更好的选择并评估这个答案。

感谢您将来的帮助:)