2013-02-26 6 views
1

Я разрабатываю контейнер UIViewController. Я хочу, чтобы контейнер делегировал управляющие контроллеры вращения.ios Custom userInterface rotation

// 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), все подвью отображаются правильно, за исключением изображений, которые остаются в исходной ориентации (портрет) и «плавают» в прокрутке. После некоторых отладки я обнаружил, что это происходит потому, что метод

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

называют

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

в свою очередь, под названием вручную

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

не получает pagingScrollView поворачивается, но всегда получает портретной ориентации , Это кажется правильным, потому что вращение еще не было применено в этот момент. Очевидно, что с использованием модальных переходов все работает нормально, но по разным причинам мне нужен контейнер для управления поворотом.

Есть ли способ распространить поворот на pagingScrollView или есть ли другой способ позволить контейнеру управлять вращением?

EDIT: небольшой обходной путь, который работает, но это не самый лучший ... исправляющие метод таким образом

- (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 изменение размера правильно. Вращение работает хорошо, но анимация получает эффект strafe на втором, третьем, ... и так далее изображении, потому что он центрируется после вращения, а не во время вращения.

ответ

0

Наконец получил лучшее wokaround :)

- (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) в строке состояния ориентирован в портретном, что не хорошо, но хорошо на данный момент

Я оставляю вопрос открытым, чтобы каждый мог легко написать лучшую альтернативу и оценить этот ответ.

Спасибо за помощь в будущем :)