コンテナ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として)表示すると、スクロールビュー内で元の向き(ポートレート)と「フロート」のままの画像を除いて、すべてのサブビューが正しく変更されます。いくつかのデバッグの後、これはメソッドが原因であることがわかりました
- (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のサイズが正しく変更されています。回転はうまく機能しますが、アニメーションは、回転が実行されている間ではなく、回転後に中央に配置されるため、2番目、3番目、...などの画像にストラフ効果があります。