I'm using viewWillLayoutSubviews to detect orientation changes but as in iOS 8 and Xcode 6 is deprecated I need to use new viewWillTransitionToSize. My problem is that I'm not able to use it.
My viewWillLayoutSubviews is:
- (void)viewWillLayoutSubviews{
NSInteger maxQuantityForWidth = MAX(3, [self.barButtons count]);
CGFloat buttonWidth = (CGRectGetWidth(self.tabBar.bounds) - (kBarButtonSeparation * (maxQuantityForWidth - 1))) / maxQuantityForWidth;
for (NSInteger i = 0; i < [self.barButtons count]; i++) {
[self.barButtons[i] setFrame:CGRectMake(i * ((buttonWidth + kBarButtonSeparation)), 2.0, buttonWidth, CGRectGetHeight(self.tabBar.bounds) - 2.0)];
}
if ([self isShowingTabBar]) {
self.functionalityContainer.frame = CGRectMake(0.0, CGRectGetHeight(self.tabBar.bounds), CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds) - CGRectGetHeight(self.tabBar.bounds));
} else {
self.functionalityContainer.frame = self.view.bounds;
}
if (IS_IPAD){
[self updateToolbar];
CGRect frame = self.slidingViewController.topViewController.view.frame;
if (LANDSCAPE) {
frame.size.width = 700;
} else {
frame.size.width = 768;
}
[self.slidingViewController.topViewController.view setFrame:frame];
}
}
and it's part of the Navigation section using ECSLidingViewController.
I tried the following:
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self updateToolbar];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
if (IS_IPAD){
CGRect frame = self.slidingViewController.topViewController.view.frame;
if (LANDSCAPE) {
NSInteger maxQuantityForWidth = MAX(3, [self.barButtons count]);
CGFloat buttonWidth = (758 - (kBarButtonSeparation * (maxQuantityForWidth - 1))) / maxQuantityForWidth;
for (NSInteger i = 0; i < [self.barButtons count]; i++) {
[self.barButtons[i] setFrame:CGRectMake(i * ((buttonWidth + kBarButtonSeparation)), 2.0, buttonWidth, CGRectGetHeight(self.tabBar.bounds) - 2.0)];
}
frame.size.width = 768;
} else {
NSInteger maxQuantityForWidth = MAX(3, [self.barButtons count]);
CGFloat buttonWidth = (758 - (kBarButtonSeparation * (maxQuantityForWidth - 1))) / maxQuantityForWidth;
for (NSInteger i = 0; i < [self.barButtons count]; i++) {
[self.barButtons[i] setFrame:CGRectMake(i * ((buttonWidth + kBarButtonSeparation)), 2.0, buttonWidth, CGRectGetHeight(self.tabBar.bounds) - 2.0)];
}
frame.size.width = 768;
}
[self.slidingViewController.topViewController.view setFrame:frame];
}
if([self isShowingTabBar]){
[self showTabBar];
}else{
[self hideTabBar];
}
if (IS_IPAD && LANDSCAPE) {
self.slidingViewController.resetStrategy = ECNone;
[self.navigationItem setLeftBarButtonItems:#[] animated:YES];
if (!self.slidingViewController.underLeftShowing) {
[self.slidingViewController anchorTopViewTo:ECRight];
}
} else {
self.slidingViewController.resetStrategy = ECTapping | ECPanning;
[self.navigationItem setLeftBarButtonItems:#[self.displayMenuBarButtonItem] animated:YES];
[self.slidingViewController resetTopView];
}
[self.slidingViewController.topViewController.view setNeedsDisplay];
[self.functionalityNavigationController.view setNeedsDisplay];
[self.view setNeedsDisplay];
}];
NSString *orientation = [[NSString alloc]initWithFormat:#"Larghezza: %f Altezza: %f", self.view.frame.size.width, self.view.frame.size.height];
[self.view makeToast:orientation duration:3.0 position:#"bottom"];
[super viewWillTransitionToSize: size withTransitionCoordinator: coordinator];
}
but it doesn't work, it seems doesn't "understand" when it's landscape or portrait. Any idea on how to fix it?
*** EDIT:
here the definitions I use
#define LANDSCAPE UIInterfaceOrientationIsLandscape(self.interfaceOrientation)
#define LANDSCAPE_RIGHT [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft
#define LANDSCAPE_LEFT [UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeRight
#define PORTRAIT UIInterfaceOrientationIsPortrait(self.interfaceOrientation)
#define PORTRAIT_REVERSE [UIDevice currentDevice].orientation == UIDeviceOrientationPortraitUpsideDown
The question seems a bit "old", anyway, at least with the introduction of iOS9 (especially multitasking), it is not recommended to draw conclusions regarding the current screen format based on device-orientation - instead, one should evaluate the current screen size. The following indicates whether the screen format is portrait or rather landscape-ish:
- (void) viewWillTransitionToSize : (CGSize) screenSize withTransitionCoordinator : (id<UIViewControllerTransitionCoordinator> _Nonnull) coordinator
{
BOOL isLandscapeOrientation = (screenSize.width > screenSize.height);
...
...
}
You should be using the trait collection method and then check the size class.
/*
This method is called when the view controller's trait collection is changed by its parent.
If you override this method, you should either call super to propagate the change to children or manually forward the change to children.
*/
- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator NS_AVAILABLE_IOS(8_0);
Related
I have a UIViewController and it's view hierarchy looks like this:
UIView
UIScrollView
UIImageView
I have code that positions the image view in the middle of the scroll view's frame, like so:
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
This works fine when zooming the content, but when the view controller first loads it does not center. This is because the scrollView.contentSize is always 0. So my question is - when should I call this method after the scrollView.contentSize is set? When does that get set?
I have tried in viewDidLayoutSubviews, and the bounds of the scroll view is set then, but not the content size. Is there some method that I can use where the scroll view will be guaranteed to have the content size set?
Or is there a better way to keep the image centered when it is smaller than the scroll view? What I am trying to accomplish is to have it so the image view is not at the top of the scroll view and what I am using works, except when the scroll view's content size is not set. But if there is a better way of doing this without having to adjust the contentInset, I would be fine with that too.
Update
Here is what I have currently.
It is almost working, but no matter what I try, I cannot get it to look correct when the view loads. The way it works now is that it starts out off-center because when it calls the recenterContent method, before the view is displayed the content size of the scroll view is CGSizeZero, so the calculations are wrong. But if I try to recenter the content after the view has been displayed, then there is a visible delay before it gets centered.
I am just confused as to when the contentSize of the scroll view is set if I am using AutoLayout constraints to specify the size.
Here is my code. Can anyone see anything wrong with it?
#interface MyImageViewController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (strong, nonatomic) UIImageView *imageView;
#property (assign, nonatomic) BOOL needsZoomScale;
#end
#implementation MyImageViewController
- (void)loadView {
self.view = [[UIView alloc] init];
[self.view addSubview:self.scrollView];
[self.scrollView addSubview:self.imageView];
self.needsZoomScale = YES;
[NSLayoutConstraint activateConstraints:#[
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
[self.scrollView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
[self.imageView.leadingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.leadingAnchor],
[self.imageView.topAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.topAnchor],
[self.imageView.trailingAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.trailingAnchor],
[self.imageView.bottomAnchor constraintEqualToAnchor:self.scrollView.contentLayoutGuide.bottomAnchor]
]];
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapZoom:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.imageView addGestureRecognizer:doubleTapGesture];
}
- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
CGRect zoomRect;
//the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
//of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
//grows.
zoomRect.size.width = scrollView.frame.size.width / scale;
zoomRect.size.height = scrollView.frame.size.height / scale;
//choose an origin so as to get the right center
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
UIView *tappedView = sender.view;
CGPoint tappedPoint = [sender locationInView:tappedView];
if (tappedPoint.x <= 0) {
tappedPoint.x = 1;
}
if (tappedPoint.y <= 0) {
tappedPoint.y = 1;
}
if (tappedPoint.x >= tappedView.bounds.size.width) {
tappedPoint.x = tappedView.bounds.size.width - 1;
}
if (tappedPoint.y >= tappedView.bounds.size.height) {
tappedPoint.y = tappedView.bounds.size.height - 1;
}
CGFloat zoomScale;
if (self.scrollView.zoomScale < 1) {
zoomScale = 1;
} else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
zoomScale = self.scrollView.maximumZoomScale;
} else {
zoomScale = self.scrollView.minimumZoomScale;
}
CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
[self.scrollView zoomToRect:zoomRect animated:YES];
}
- (UIScrollView *)scrollView {
if (!self->_scrollView) {
self->_scrollView = [[UIScrollView alloc] init];
self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self->_scrollView.minimumZoomScale = 0.1f;
self->_scrollView.maximumZoomScale = 4.0f;
self->_scrollView.bounces = YES;
self->_scrollView.bouncesZoom = YES;
self->_scrollView.delegate = self;
self->_scrollView.backgroundColor = [UIColor blackColor];
}
return self->_scrollView;
}
- (UIImageView *)imageView {
if (!self->_imageView) {
self->_imageView = [[UIImageView alloc] init];
self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
self->_imageView.userInteractionEnabled = YES;
}
return self->_imageView;
}
- (UIImage *)image {
return self.imageView.image;
}
- (void)setImage:(UIImage *)image {
self.imageView.image = image;
self.needsZoomScale = YES;
[self updateZoomScale];
}
- (void)updateZoomScale {
if (self.needsZoomScale && self.image) {
CGSize size = self.view.bounds.size;
if (size.width == 0.0f || size.height == 0.0f) {
return;
}
UIImage *image = self.image;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
if (imageSize.width > 0 && imageSize.height > 0) {
CGFloat widthScale = size.width / imageSize.width;
CGFloat heightScale = size.height / imageSize.height;
CGFloat minScale = MIN(widthScale, heightScale);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
self.needsZoomScale = NO;
}
}
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
[self updateZoomScale];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[self recenterContent:self.scrollView];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self recenterContent:self.scrollView];
}
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
#end
The problem is that a UIImageView has an intrinsic content size of 0,0 -- so your code is initially putting the a 0x0 image view at the center of the scroll view.
I've made a few changes to the code you posted... see comments (I "wrapped" the changes in
// ---------------------------------
comment lines:
#interface MyImageViewController : UIViewController <UIScrollViewDelegate>
#end
#interface MyImageViewController ()
#property (strong, nonatomic) UIScrollView *scrollView;
#property (strong, nonatomic) UIImageView *imageView;
#property (assign, nonatomic) BOOL needsZoomScale;
#end
#implementation MyImageViewController
- (void)loadView {
self.view = [[UIView alloc] init];
[self.view addSubview:self.scrollView];
[self.scrollView addSubview:self.imageView];
self.needsZoomScale = YES;
// ---------------------------------
// respect safe area
UILayoutGuide *g = [self.view safeAreaLayoutGuide];
// saves on a little typing
UILayoutGuide *sg = [self.scrollView contentLayoutGuide];
// ---------------------------------
[NSLayoutConstraint activateConstraints:#[
[self.scrollView.leadingAnchor constraintEqualToAnchor:g.leadingAnchor],
[self.scrollView.topAnchor constraintEqualToAnchor:g.topAnchor],
[self.scrollView.trailingAnchor constraintEqualToAnchor:g.trailingAnchor],
[self.scrollView.bottomAnchor constraintEqualToAnchor:g.bottomAnchor],
[self.imageView.leadingAnchor constraintEqualToAnchor:sg.leadingAnchor],
[self.imageView.topAnchor constraintEqualToAnchor:sg.topAnchor],
[self.imageView.trailingAnchor constraintEqualToAnchor:sg.trailingAnchor],
[self.imageView.bottomAnchor constraintEqualToAnchor:sg.bottomAnchor]
]];
}
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapZoom:)];
doubleTapGesture.numberOfTapsRequired = 2;
[self.imageView addGestureRecognizer:doubleTapGesture];
}
- (CGRect)zoomRectForScrollView:(UIScrollView *)scrollView withScale:(CGFloat)scale withCenter:(CGPoint)center {
CGRect zoomRect;
//the zoom rect is in the content view's coordinates. At a zoom scale of 1.0, the zoom rect would be the size
//of the scroll view's bounds. As the zoom scale decreases, so more content is visible, the size of the rect
//grows.
zoomRect.size.width = scrollView.frame.size.width / scale;
zoomRect.size.height = scrollView.frame.size.height / scale;
//choose an origin so as to get the right center
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
- (void)doubleTapZoom:(UITapGestureRecognizer *)sender {
UIView *tappedView = sender.view;
CGPoint tappedPoint = [sender locationInView:tappedView];
if (tappedPoint.x <= 0) {
tappedPoint.x = 1;
}
if (tappedPoint.y <= 0) {
tappedPoint.y = 1;
}
if (tappedPoint.x >= tappedView.bounds.size.width) {
tappedPoint.x = tappedView.bounds.size.width - 1;
}
if (tappedPoint.y >= tappedView.bounds.size.height) {
tappedPoint.y = tappedView.bounds.size.height - 1;
}
CGFloat zoomScale;
if (self.scrollView.zoomScale < 1) {
zoomScale = 1;
} else if (self.scrollView.zoomScale < self.scrollView.maximumZoomScale) {
zoomScale = self.scrollView.maximumZoomScale;
} else {
zoomScale = self.scrollView.minimumZoomScale;
}
CGRect zoomRect = [self zoomRectForScrollView:self.scrollView withScale:zoomScale withCenter:tappedPoint];
[self.scrollView zoomToRect:zoomRect animated:YES];
}
- (UIScrollView *)scrollView {
if (!self->_scrollView) {
self->_scrollView = [[UIScrollView alloc] init];
self->_scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self->_scrollView.minimumZoomScale = 0.1f;
self->_scrollView.maximumZoomScale = 4.0f;
self->_scrollView.bounces = YES;
self->_scrollView.bouncesZoom = YES;
self->_scrollView.delegate = self;
self->_scrollView.backgroundColor = [UIColor blackColor];
}
return self->_scrollView;
}
- (UIImageView *)imageView {
if (!self->_imageView) {
self->_imageView = [[UIImageView alloc] init];
self->_imageView.translatesAutoresizingMaskIntoConstraints = NO;
self->_imageView.userInteractionEnabled = YES;
}
return self->_imageView;
}
- (UIImage *)image {
return self.imageView.image;
}
- (void)setImage:(UIImage *)image {
self.imageView.image = image;
// ---------------------------------
// set the frame here
self.imageView.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
// ---------------------------------
// not needed ... unless maybe changing the image while view is showing?
//self.needsZoomScale = YES;
//[self updateZoomScale];
}
- (void)updateZoomScale {
if (self.needsZoomScale && self.image) {
CGSize size = self.view.bounds.size;
if (size.width == 0.0f || size.height == 0.0f) {
return;
}
UIImage *image = self.image;
CGSize imageSize = CGSizeMake(image.size.width * image.scale, image.size.height * image.scale);
if (imageSize.width > 0 && imageSize.height > 0) {
CGFloat widthScale = size.width / imageSize.width;
CGFloat heightScale = size.height / imageSize.height;
CGFloat minScale = MIN(widthScale, heightScale);
self.scrollView.minimumZoomScale = minScale;
self.scrollView.zoomScale = minScale;
self.needsZoomScale = NO;
}
}
}
// ---------------------------------
// Don't need this
//- (void)viewWillLayoutSubviews {
// [super viewWillLayoutSubviews];
// [self updateZoomScale];
//}
// ---------------------------------
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// ---------------------------------
// update zoom scale here
[self updateZoomScale];
// ---------------------------------
[self recenterContent:self.scrollView];
}
// ---------------------------------
// Don't need this
//- (void)viewDidAppear:(BOOL)animated {
// [super viewDidAppear:animated];
// [self recenterContent:self.scrollView];
//}
// ---------------------------------
#pragma mark - UIScrollViewDelegate
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
[self recenterContent:scrollView];
}
- (void)recenterContent:(UIScrollView *)scrollView {
//this centers the content when it is smaller than the scrollView's bounds
CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);
self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
#end
and here's how I call it:
MyImageViewController *vc = [MyImageViewController new];
UIImage *img = [UIImage imageNamed:#"bkg"];
if (nil == img) {
NSLog(#"Could not load image!!!!");
return;
}
[vc setImage:img];
[self.navigationController pushViewController:vc animated:YES];
I know this question has been asked earlier but i am not getting any specific answer to my problem. I am using page control and scrollView to add view controllers and then showing all the pages using swipe. I need to redirect to a particular page on button click. I tried scrollView SetcontentOffet and every other possiblities.
-(void)ScrollToPage:(int)page
{
UIViewController *controller = [self.childViewControllers objectAtIndex:page];
if (controller.view.superview == nil)
{
CGRect frame = self.scrollView.frame;
if (frame.size.width == 0)
{
frame.size.width = self.view.frame.size.width;
frame.size.height = self.view.frame.size.height;
}
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView setContentOffset:CGPointMake(frame.origin.x, frame.origin.y) animated:YES];
self.pageControl.currentPage = page;
[self centerScrollViewContents];
[self.scrollView setContentOffset:CGPointMake(frame.origin.x, frame.origin.y)];
[self.scrollView scrollRectToVisible:frame animated:YES];
[self.scrollView addSubview:controller.view];
}
}
I assume that your pages are using your device width closely. Just follow the below steps.
- (void)viewWillAppear:(BOOL)animated {
self.myCurrentPage = 0;
[self setScrollViewPages];
self.scrollView.delegate = self;
}
- (void)setScrollViewPages {
for (int i = 0; i < numberOfPages; i++) {
UIView *myPageView = [[UIView alloc]initWithFrame:(CGRect) {self.view.frame.size.width * i
+ 10.0f,10.0f,self.view.frame.size.width - 20.0f , self.scrollView.frame.size.height-20.0f}];
myPageView.backgroundColor = [UIColor greenColor];
[self.scrollView addSubview:myPageView];
}
}
On Button Clicked
- (IBAction)buttonClicked:(id)sender {
//Pass the requried page, by default I'm passing 4
self.myCurrentPage = 4;
[self updateMyScrollView];
}
And the respective scroll view setting is
- (void)updateMyScrollView {
[self.scrollView setContentOffset:CGPointMake(self.view.frame.size.width *self.myCurrentPage, 0) animated:YES];
self.pageControl.currentPage = self.myCurrentPage;
}
Additionally, your scrollview delegate would be like this...
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
int pageWidth = self.view.frame.size.width ;
int pageX = self.myCurrentPage*pageWidth - scrollView.contentInset.left;
if (targetContentOffset->x<pageX) {
if (self.myCurrentPage>0) {
self.myCurrentPage--;
}
}
else if(targetContentOffset->x>pageX){
if (self.myCurrentPage < numerOfPages) {
self.myCurrentPage++;
}
}
targetContentOffset->x = self.myCurrentPage*pageWidth-scrollView.contentInset.left;
DLog(#"%ld %d", (long)self.myCurrentPage, (int)targetContentOffset->x);
self.pageControl.currentPage = self.myCurrentPage;
}
Hope this helps....
I have an Extended UICollectionFlowLayout. This vertically centres the UIcollectionViewCell by translating the attribute.frame by required amount and also shifting the visible Rect of collection view to show the transformed cells.
This works perfectly fine in ios7. However in ios6 the visible Rect of collection view does not change , hence forth cells are shown shifted but clipped.
Eg : -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect rect = (0,0,320,500) and I shift cells by 200 then cells with start showing from (0,0,320,200) to (0,0,320,500) and those below 500 will be clipped. Any reason why this would happen in ios6 when it work perfectly in iOS7 ?
#implementation VerticallyCenteredFlowLayout
-(id)init
{
if (!(self = [super init])) return nil;
[self setMinimumLineSpacing:5.0];
[self setMinimumInteritemSpacing:0.0];
[self setItemSize:CGSizeMake(10, 10)];
[self setSectionInset:UIEdgeInsetsMake(0, 11, 11, 11)];
[self setScrollDirection:UICollectionViewScrollDirectionVertical];
return self;
}
-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* array = [super layoutAttributesForElementsInRect:rect];
UICollectionViewLayoutAttributes* att = [array lastObject];
if (att){
CGFloat lastY = att.frame.origin.y + att.frame.size.height;
CGFloat diff = self.collectionView.frame.size.height - lastY;
if (diff > 0){
for (UICollectionViewLayoutAttributes* a in array){
a.frame = CGRectMake(a.frame.origin.x, a.frame.origin.y + diff/2, a.frame.size.width, a.frame.size.height) ;
}
}
}
return array;
}
The contentSize was not being automatically Adjusted in ios6.
Overiding following method in VerticallyCenteredFlowLayout Class fixed the issue
-(CGSize)collectionViewContentSize {
CGSize size = [super collectionViewContentSize];
if (size.height < MIN(_maxHeight,self.collectionView.frame.size.height)) {
size.height = MIN(_maxHeight,self.collectionView.frame.size.height);
}
return size;
}
Can anybody help me with the sample code for embedding a zoomable scroll view within a paging scroll view so that each page can be zoomed and panned individually?
Also the navigation structure is like tab bar controller --> navigation controller with buttons (on push of the buttons) --> view controller where in the horizontal scrollview of images has to be implemented with pagination and zooming.
Here is the link to the tutorial that I followed: How To Use UIScrollView to Scroll and Zoom Content
The code I've implemented is following:
#import "ViewCorpBrochureController.h"
#interface ViewCorpBrochureController ()
#property (nonatomic, strong) NSArray *pageImages;
#property (nonatomic, strong) NSMutableArray *pageViews;
#property (nonatomic, strong) UIImageView *imageView;
- (void)loadVisiblePages;
- (void)loadPage:(NSInteger)page;
- (void)purgePage:(NSInteger)page;
- (void)centerScrollViewContents;
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer;
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer;
#end
#implementation ViewCorpBrochureController
#synthesize scrollView = _scrollView;
#synthesize pageControl = _pageControl;
#synthesize pageImages = _pageImages;
#synthesize pageViews = _pageViews;
#synthesize imageView = _imageview;
- (void)centerScrollViewContents {
CGSize boundsSize = self.scrollView.bounds.size;
CGRect contentsFrame = self.imageView.frame;
if (contentsFrame.size.width < boundsSize.width) {
contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0f;
} else {
contentsFrame.origin.x = 0.0f;
}
if (contentsFrame.size.height < boundsSize.height) {
contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0f;
} else {
contentsFrame.origin.y = 0.0f;
}
self.imageView.frame = contentsFrame;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
// Get the location within the image view where we tapped
CGPoint pointInView = [recognizer locationInView:self.imageView];
// Get a zoom scale that's zoomed in slightly, capped at the maximum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale * 1.5f;
newZoomScale = MIN(newZoomScale, self.scrollView.maximumZoomScale);
// Figure out the rect we want to zoom to, then zoom to it
CGSize scrollViewSize = self.scrollView.bounds.size;
CGFloat w = scrollViewSize.width / newZoomScale;
CGFloat h = scrollViewSize.height / newZoomScale;
CGFloat x = pointInView.x - (w / 2.0f);
CGFloat y = pointInView.y - (h / 2.0f);
CGRect rectToZoomTo = CGRectMake(x, y, w, h);
[self.scrollView zoomToRect:rectToZoomTo animated:YES];
}
- (void)scrollViewTwoFingerTapped:(UITapGestureRecognizer*)recognizer {
// Zoom out slightly, capping at the minimum zoom scale specified by the scroll view
CGFloat newZoomScale = self.scrollView.zoomScale / 1.5f;
newZoomScale = MAX(newZoomScale, self.scrollView.minimumZoomScale);
[self.scrollView setZoomScale:newZoomScale animated:YES];
}
#pragma mark -
- (void)loadVisiblePages {
// First, determine which page is currently visible
CGFloat pageWidth = self.scrollView.frame.size.width;
NSInteger page = (NSInteger)floor((self.scrollView.contentOffset.x * 2.0f + pageWidth) / (pageWidth * 2.0f));
// Update the page control
self.pageControl.currentPage = page;
// Work out which pages we want to load
NSInteger firstPage = page - 1;
NSInteger lastPage = page + 1;
// Purge anything before the first page
for (NSInteger i=0; i<firstPage; i++) {
[self purgePage:i];
}
for (NSInteger i=firstPage; i<=lastPage; i++) {
[self loadPage:i];
}
for (NSInteger i=lastPage+1; i<self.pageImages.count; i++) {
[self purgePage:i];
}
}
- (void)loadPage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what we have to display, then do nothing
return;
}
// Load an individual page, first seeing if we've already loaded it
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView == [NSNull null]) {
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0.0f;
UIImageView *newPageView = [[UIImageView alloc] initWithImage:[self.pageImages objectAtIndex:page]];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.frame = frame;
[self.scrollView addSubview:newPageView];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
- (void)purgePage:(NSInteger)page {
if (page < 0 || page >= self.pageImages.count) {
// If it's outside the range of what we have to display, then do nothing
return;
}
// Remove a page from the scroll view and reset the container array
UIView *pageView = [self.pageViews objectAtIndex:page];
if ((NSNull*)pageView != [NSNull null]) {
[pageView removeFromSuperview];
[self.pageViews replaceObjectAtIndex:page withObject:[NSNull null]];
}
}
#pragma mark -
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"CorporateBrochure";
self.pageImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"CB1.jpg"],
[UIImage imageNamed:#"CB2.jpg"],
[UIImage imageNamed:#"CB3.jpg"],
[UIImage imageNamed:#"CB4.jpg"],
[UIImage imageNamed:#"CB5.jpg"],
[UIImage imageNamed:#"CB6.jpg"],
nil];
NSInteger pageCount = self.pageImages.count;
// Set up the page control
self.pageControl.currentPage = 0;
self.pageControl.numberOfPages = pageCount;
// Set up the array to hold the views for each page
self.pageViews = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < pageCount; ++i) {
[self.pageViews addObject:[NSNull null]];
}
/* If I use this the subview appears below the image
UIImage *image = self.imageView.image;
//UIImage *image = [UIImage imageNamed:#"CB2.jpg"];
self.imageView = [[UIImageView alloc] initWithImage:image];
self.imageView.frame = (CGRect){.origin=CGPointMake(0.0f, 0.0f), .size=image.size};
[self.scrollView addSubview:self.imageView];
self.scrollView.contentSize = image.size;
*/
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[self.scrollView addGestureRecognizer:doubleTapRecognizer];
UITapGestureRecognizer *twoFingerTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewTwoFingerTapped:)];
twoFingerTapRecognizer.numberOfTapsRequired = 1;
twoFingerTapRecognizer.numberOfTouchesRequired = 2;
[self.scrollView addGestureRecognizer:twoFingerTapRecognizer];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGSize pagesScrollViewSize = self.scrollView.frame.size;
self.scrollView.contentSize = CGSizeMake(pagesScrollViewSize.width * self.pageImages.count, pagesScrollViewSize.height);
[self loadVisiblePages];
}
- (void)viewDidUnload {
[super viewDidUnload];
self.scrollView = nil;
self.pageControl = nil;
self.pageImages = nil;
self.pageViews = nil;
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
// Return the view that we want to zoom
return self.imageView;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationMaskAll);
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// Load the pages which are now on screen
[self loadVisiblePages];
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
// The scroll view has zoomed, so you need to re-center the contents
[self centerScrollViewContents];
}
#end
Is there a specific issue you are having with Ray Wenderlich's tutorial?
Apple's documentation has a very good example of how to implement a paging scroll view, see the Photo Scroller sample code here. I would suggest starting there.
From the above link:
"PhotoScroller" demonstrates the use of embedded UIScrollViews and
CATiledLayer to create a rich user experience for displaying and
paginating photos that can be individually panned and zoomed.
Note: If your images are not overly large, you can get away with not using CATiledLayer.
EDIT 1: See my answer to this question about how to modify Apple's Photo Scroller code to make the UIPageViewController a subview of your own view controller (and not the rootViewController).
EDIT 2: See this sample project on github.
I am developing a container UIViewController.
I wish the container to delegate the management controllers of the 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];
}
So far so good.
Now I would like the container to remain always in portrait and rotate its contentView to control the rotation, then patching method like this:
// 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;
}
the currentController is an instance of the PhotoViewController (old style of the Apple example PhotoScroller with some modifications) wich is:
/*
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
and the ".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
Ok, now the problem. When I introduce the PhotoViewController modally, the rotation is correct and the pictures show the correct position.
When, however, I present the PhotoViewController in the container (so as its contentView) all subview are changed correctly except the images that remain in their original orientation (portrait) and "float" within the scrollview. After some debugging I found that this is because the method
- (CGRect) frameForPageAtIndex: (NSUInteger) index {...}
called by
- (void) willAnimateRotationToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration {...}
in turn called manually by
- (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) {...} toInterfaceOrientation container
does not receive the pagingScrollView rotated but always receives portrait orientation. Which seems correct because the rotation has not yet been applied at that moment. Obviously, using the modal transitions, everything works fine but, for various reasons, I need the container to manage the rotation.
Is there any way to propagate the rotation to pagingScrollView or is there another way to let container control the rotation?
EDIT: a small workaround that works but is not the best ... Correcting the method in this manner
- (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];
...
}
I get that pagingScrollView being resized correctly. The rotation work well but the animation get a strafe effect on the second,third, ... and so on image because it is centered after the rotation and not while the rotation is performing.
Finally got a better 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];
}];
...
}
Since the image is rotated after and not during the animation, simply, I moved the call to willAnimateEtc inside the animation block. In this way I get a rotation very similar to that carried out automatically by the OS. The only things still remain are:
1) a slight flick on the right side of the image, but it is quite acceptable
2) the status bar is oriented in portrait, which is not fine, but ok for now
I leave the question open so that anyone can write easily a better alternative and evaluate this answer.
Thanks for the future help :)