1

コンテナ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番目、...などの画像にストラフ効果があります。

4

1 に答える 1

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 の呼び出しをアニメーション ブロック内に移動しました。このようにして、OS によって自動的に実行される回転と非常によく似た回転が得られます。まだ残っているのは次のとおりです。

1) 画像の右側にわずかなフリックがありますが、許容範囲内です

2) ステータス バーが縦向きになっています。これは問題ありませんが、今のところは問題ありません。

誰でも簡単により良い代替案を書き、この回答を評価できるように、質問を開いたままにします。

将来の助けをありがとう:)

于 2013-02-26T16:54:04.310 に答える