ScrollView & ImageView - Image not centered after multiple device rotations - ios

I've got a segmentedControl with three views in my app, one of which is a scrollView which works like a sort of gallery without zoom, with pageControl and an imageView at the center.
The hierarchy is like
--> Segmented Control (3 views) : descriptionView, imageTabView, shareView
----> imagesTabView (UIView)
------> scrollView
------> imageView
----> pageControl
When the device is portrait or landscape, the imageView images are shown correctly, they're centered and scrolling works perfectly fine.
The only problem is that when you turn the device again, if the image is "in the middle" (e.g. is the 2nd of 3 or the 3rd of 6), it's being shown decentered, far left or right, and with a little swipe it goes back at the center, while if the image is the first or the last one, it works properly.
I've looked here on S.O. on various threads, tried to set a contentView as a subview of the scrollView and add the imageView as subview of contentView, but didn't work, tried to attach the imageView to the bottom or the right of the scrollView but didn't work either.
I feel like I'm a step away to achieve what I want to do, the only problem is that I can't get why it's not centered.
In viewWillLayoutSubviews I've specified the contentSize, in order that when it rotates, the size it's set correctly, like
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
self.scrollView.contentSize = CGSizeMake (self.scrollView.frame.size.width * photosArray.count, 1);
}
Here's how I'm initializing the pageControl, the scrollView and the imageView:
-(void)configureImageTab{
pageControl = [UIPageControl new];
[pageControl addTarget:self action:#selector(changePage) forControlEvents:UIControlEventValueChanged];
pageControl.translatesAutoresizingMaskIntoConstraints = NO;
//Don't show pageControl when there are no photos
if (photosURL.count == 0)
pageControl.hidden = YES;
//Configuring scrollView
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)];
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.userInteractionEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//... Code cut - adding remote images to fetch to array
//Actual setup -> scrollView adding imageView as subview with all the images
for (int i =0; i< photosArray.count; i++){
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
//imageView setup
imageView = [[UIImageView alloc]initWithFrame:frame];
imageView.backgroundColor = [UIColor clearColor];
imageView.clipsToBounds = YES;
imageView.userInteractionEnabled = YES;
imageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
imageView.translatesAutoresizingMaskIntoConstraints = YES;
imageView.contentMode = UIViewContentModeScaleAspectFit;
//Setting images urls
[imageView setImageWithURL:[NSURL URLWithString:[photosArray objectAtIndex:i]] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
//Error handling
}
}usingActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
//Adding gesture recognizer to scrollView and imageView as subview
[self.scrollView addGestureRecognizer:singleTap];
[self.scrollView addSubview:imageView];
}
//Setting the contentSize
pageControl.numberOfPages = [photosURL count];
[self.imageSegmentView addSubview:self.scrollView];
[self.imageSegmentView addSubview:pageControl];
//Constraints
NSDictionary *views = #{#"pageControl" : pageControl, #"scrollView" : self.scrollView};
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:nil views:views]];
[pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]];
}
#pragma mark - scrollView delegate -
-(void)scrollViewDidScroll:(UIScrollView *)sView{
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor ((self.scrollView.contentOffset.x - pageWidth /2) /pageWidth) +1;
self.pageControl.currentPage = page;
}
-(IBAction)changePage {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * self.pageControl.currentPage;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
[self.scrollView scrollRectToVisible:frame animated:YES];
}
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
pageControlBeingUsed = NO;
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
pageControlBeingUsed = NO;
}
One note to make: imageView is using autoresizingMask: without that, it wouldn't be able to show the images properly.
My guess is that probably there's something to fix within the scrollView delegate, but I'm not quite sure.
Any suggestion appreciated!
EDIT
I've noticed that the same bug occurs in Twitter app when browsing a user's pictures and then turning the device.
EDIT 2 for TL;DR
Basically, let's say I have 3 images in an horizontal scrollView with paging.
I turn the device from Portrait to Landscape on the first photo, and it's shown at its own place, correctly centered.
I move to the next photo, shown centered, and then I turn the device again to Portrait. The photo is not aligned correctly, is not centered
Practically, the first and the last images, when the device rotates multiple times, are shown centered. The others are not centered
EDIT 3
I've extracted some of the lines and made a sample project to demonstrate the issue I'm having. I guess there's definitely something up with contentSize.

We can fix the specific bug you're talking about (scroll view not aligned to page boundary after rotation) by recording the current page when the interface is about to rotate, and then setting the scroll view's contentOffset appropriately during the rotation, after the system has updated the scroll view's bounds size. Let's add a pageNumberPriorToRotation instance variable:
#implementation ViewController {
CGFloat pageNumberPriorToRotation;
}
Then, we set it when the interface is about to rotate:
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self setPageNumberPriorToRotation];
}
- (void)setPageNumberPriorToRotation {
CGRect bounds = self.scrollView.bounds;
static const int kNumberOfImages = 3;
pageNumberPriorToRotation = fmin(round(bounds.origin.x / bounds.size.width),
kNumberOfImages - 1);
}
and we use it to set the scroll view's contentOffset during the interface rotation:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
[self updateScrollViewLayout];
}
- (void)updateScrollViewLayout {
CGRect bounds = self.scrollView.bounds;
bounds.origin.x = bounds.size.width * pageNumberPriorToRotation;
self.scrollView.bounds = bounds;
}
This takes care of your primary complaint: the scroll view will always be aligned to a page view boundary after a rotation.
However…
There are some other problems with the scroll view interaction. In landscape orientation, I can't scroll to the third image. After rotating to landscape and back to portrait, I can scroll to a blank fourth page. These problems are presumably what you meant by “there's definitely something up with contentSize”.
Furthermore, your code has a number of problems. It uses some outdated style, like explicitly declaring instance variables for properties and putting instance variables in the header file. It also suffers from Massive View Controller. It could really stand to be rewritten in modern style, and using features like UITabBarController and UIPageViewController.
Anyway, you probably have neither the time nor the inclination to do that amount of work, so I will show you how to solve the contentSize problems and slim down your VC a little at the same time.
I'll make a UIScrollView subclass called ImageScrollView. You give me the array of images and I'll take care of setting up its subviews and aligning to a page boundary after a rotation. Here's my header file:
ImageScrollView.h
#import <UIKit/UIKit.h>
#interface ImageScrollView : UIScrollView
#property (nonatomic, copy) NSArray *images;
#end
To implement this, I'll need some instance variables:
ImageScrollView.m
#import "ImageScrollView.h"
#import <tgmath.h>
#implementation ImageScrollView {
NSMutableArray *imageSubviews;
CGSize priorSize;
CGFloat pageNumber;
BOOL needsToSyncSubviewsWithImages : 1;
}
Anyway, first I'll implement the public API, which is just the images property:
#pragma mark - Public API
#synthesize images = _images;
- (void)setImages:(NSArray *)images {
_images = [images copy];
needsToSyncSubviewsWithImages = YES;
}
Note that when you set the images array, I don't immediately create the subviews. For now, I just set the needsToSyncSubviewsWithImages flag so I'll know to do it during the layout phase.
#pragma mark - UIView overrides
Next, I need to override layoutSubviews so I can do the real work during the layout phase. The system sends me layoutSubviews during the layout phase if my subviews array has changed, or if my bounds has changed.
Because I'm a scroll view, and because a scroll view's contentOffset is really just an alias for its bounds.origin, the system sends me layoutSubviews a lot: every time the scroll view scrolls. So I want to be careful to do only necessary work in layoutSubviews.
- (void)layoutSubviews {
The first thing I do is call super, which takes lets auto layout work (if you're using it) and updates my scroll indicators (if they're visible).
[super layoutSubviews];
Next, if I got new images, I set up the subviews that display them.
if (needsToSyncSubviewsWithImages) {
[self syncSubviewsWithImages];
}
Next, if I've set up new subviews, or if I've changed size, I lay out my subviews' frames for the new size, and align to a page boundary.
if (needsToSyncSubviewsWithImages || !CGSizeEqualToSize(self.bounds.size, priorSize)) {
[self layoutForNewSize];
}
Finally, I update my state.
needsToSyncSubviewsWithImages = NO;
priorSize = self.bounds.size;
[self updatePageNumber];
}
Of course, I delegated all the real work to helper methods, so now I need to implement those.
#pragma mark - Implementation details
To synchronize my subviews with my images, I need to do three things. I need to make sure I've actually allocated my imageSubviews array, I need to make sure every image is in a subview, and I need to make sure I don't have any extra image subviews (in case my images array was made smaller).
- (void)syncSubviewsWithImages {
[self ensureImageSubviewsArrayExists];
[self putImagesInSubviews];
[self removeExtraSubviews];
}
- (void)ensureImageSubviewsArrayExists {
if (imageSubviews == nil) {
imageSubviews = [NSMutableArray arrayWithCapacity:self.images.count];
}
}
- (void)putImagesInSubviews {
[self.images enumerateObjectsUsingBlock:^(id obj, NSUInteger i, BOOL *stop) {
[self putImage:obj inSubviewAtIndex:i];
}];
}
- (void)removeExtraSubviews {
while (imageSubviews.count > self.images.count) {
[imageSubviews.lastObject removeFromSuperview];
[imageSubviews removeLastObject];
}
}
- (void)putImage:(UIImage *)image inSubviewAtIndex:(NSUInteger)i {
UIImageView *imageView = [self imageViewAtIndex:i];
imageView.image = image;
}
When I want to get the image view for an index, I might find that I haven't actually created enough subviews yet, so I create them on demand:
- (UIImageView *)imageViewAtIndex:(NSUInteger)i {
while (i >= imageSubviews.count) {
UIView *view = [[UIImageView alloc] init];
view.contentMode = UIViewContentModeScaleAspectFit;
view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
[self addSubview:view];
[imageSubviews addObject:view];
}
return imageSubviews[i];
}
Note that I've set the autoresizingMask such that autoresizing won't actually modify my subview frames. Instead, I'll lay them out “manually”.
OK, now I need to implement the methods that set my subviews' frames and align to a page boundary when my size changes.
- (void)layoutForNewSize {
[self setSubviewFramesAndContentSize];
[self alignToNearestPage];
}
Setting the subview frames requires looping over them, laying them out from left to right. After I've laid out the last one, I know my contentSize. Note that I need to loop over imageSubviews only, not self.subviews, because self.subviews also contains the scroll indicators.
- (void)setSubviewFramesAndContentSize {
CGRect frame = self.bounds;
frame.origin = CGPointZero;
for (UIView *subview in imageSubviews) {
subview.frame = frame;
frame.origin.x += frame.size.width;
}
self.contentSize = CGSizeMake(frame.origin.x, frame.size.height);
}
To align to the nearest page, I set my contentOffset based on the last known page number and my new size.
- (void)alignToNearestPage {
self.contentOffset = CGPointMake(pageNumber * self.bounds.size.width, 0);
}
Finally, I need to update my page number every time I scroll, so I'll have it in case of rotation:
- (void)updatePageNumber {
// Note that self.contentOffset == self.bounds.origin.
CGRect bounds = self.bounds;
pageNumber = fmin(round(bounds.origin.x / bounds.size.width), self.images.count - 1);
}
#end
Now you can update ViewController to use the ImageScrollView. This mostly involves ripping stuff out:
-(void)configureImageTab{
//Page control
pageControl = [UIPageControl new];
pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
pageControl.pageIndicatorTintColor = [UIColor grayColor];
[pageControl addTarget:self action:#selector(changePage) forControlEvents:UIControlEventValueChanged];
pageControl.translatesAutoresizingMaskIntoConstraints = NO;
//Configuring scrollView
self.scrollView = [[ImageScrollView alloc] initWithFrame:CGRectMake(0, 0, self.imageSegmentView.frame.size.width, self.imageSegmentView.frame.size.height-pageControl.frame.size.height)];
self.scrollView.backgroundColor = [UIColor clearColor];
self.scrollView.pagingEnabled = YES;
self.scrollView.delegate = self;
self.scrollView.userInteractionEnabled = YES;
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
//Adding imageURLS to array
photos = #[ [UIImage imageNamed:#"createBootableUSBInstallDrive1"], [UIImage imageNamed:#"createBootableUSBInstallDrive2"], [UIImage imageNamed:#"createBootableUSBInstallDrive3"]];
self.scrollView.images = photos;
pageControl.numberOfPages = [photos count];
[self.imageSegmentView addSubview:self.scrollView];
[self.imageSegmentView addSubview:pageControl];
NSDictionary *views = #{#"pageControl" : pageControl, #"scrollView" : self.scrollView};
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[pageControl]-0-|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics:nil views:views]];
[self.imageSegmentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]-1-[pageControl]-1-|" options:0 metrics:nil views:views]];
[pageControl addConstraint:[NSLayoutConstraint constraintWithItem:pageControl attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.imageSegmentView attribute:NSLayoutAttributeHeight multiplier:0 constant:30]];
}
You also need to change the declared type of scrollView to ImageScrollView in the header file. You can eliminate the viewWillLayoutSubviews, willRotateToInterfaceOrientation:duration:, and willAnimateRotationToInterfaceOrientation:duration: methods entirely.
I've uploaded my modified version of your test project to this github repository.

Related

Date pickers selection's overlay not adjusting when screen rotates

I've got a view with two date pickers set to cover the entire view's height and half a width each, so that they are filling the entire view.
I've then added an overlay to each picker to make the selection more visible, like this:
-(void)drawOverlays {
if (_overlay1 != nil) {
[_overlay1 removeFromSuperview];
}
if (_overlay2 != nil) {
[_overlay2 removeFromSuperview];
}
_overlay1 = [[UIView alloc] initWithFrame:CGRectMake(_startPicker.bounds.origin.x, (_startPicker.frame.size.height/2)-19, _startPicker.bounds.size.width, 38)];
_overlay1.backgroundColor = [UIColor redColor];
_overlay1.alpha = 0.5f;
[_startPicker addSubview:_overlay1];
_overlay2 = [[UIView alloc] initWithFrame:CGRectMake(_endPicker.bounds.origin.x, (_endPicker.frame.size.height/2)-19, _endPicker.bounds.size.width, 38)];
_overlay2.backgroundColor = [UIColor redColor];
_overlay2.alpha = 0.5f;
[_endPicker addSubview:_overlay2];
}
I'm calling this method from the -viewDidLayoutSubviews method and from the -viewWillTransitionToSize:withTransitionCoordinator method, and the first time the view appears everything is fine.
Then I rotate my iPad and the overlays are shown inverted, meaning that when in landscape the overlays are the size I want for the portrait and vice versa.
What's wrong with my code?
You will be much better off using constraints and letting auto-layout handle the resizing:
-(void)drawOverlays {
if (_overlay1 != nil) {
[_overlay1 removeFromSuperview];
}
if (_overlay2 != nil) {
[_overlay2 removeFromSuperview];
}
//_overlay1 = [[UIView alloc] initWithFrame:CGRectMake(_startPicker.bounds.origin.x, (_startPicker.frame.size.height/2)-19, _startPicker.bounds.size.width, 38)];
// instantiate overlay1
_overlay1 = [UIView new];
_overlay1.backgroundColor = [UIColor redColor];
_overlay1.alpha = 0.5f;
// add as subview of startPicker
[_startPicker addSubview:_overlay1];
//_overlay2 = [[UIView alloc] initWithFrame:CGRectMake(_endPicker.bounds.origin.x, (_endPicker.frame.size.height/2)-19, _endPicker.bounds.size.width, 38)];
// instantiate overlay2
_overlay2 = [UIView new];
_overlay2.backgroundColor = [UIColor redColor];
_overlay2.alpha = 0.5f;
// add as subview of endPicker
[_endPicker addSubview:_overlay2];
// we want to use auto-layout / constraints
_overlay1.translatesAutoresizingMaskIntoConstraints = NO;
_overlay2.translatesAutoresizingMaskIntoConstraints = NO;
[NSLayoutConstraint activateConstraints:#[
// constrain overlay1 to startPicker
// centerY
// leading / trailing = 0
// height = 38
[_overlay1.centerYAnchor constraintEqualToAnchor:_startPicker.centerYAnchor],
[_overlay1.leadingAnchor constraintEqualToAnchor:_startPicker.leadingAnchor constant:0.0],
[_overlay1.trailingAnchor constraintEqualToAnchor:_startPicker.trailingAnchor constant:0.0],
[_overlay1.heightAnchor constraintEqualToConstant:38.0],
// constrain overlay2 to startPicker
// centerY
// leading / trailing = 0
// height = 38
[_overlay2.centerYAnchor constraintEqualToAnchor:_endPicker.centerYAnchor],
[_overlay2.leadingAnchor constraintEqualToAnchor:_endPicker.leadingAnchor constant:0.0],
[_overlay2.trailingAnchor constraintEqualToAnchor:_endPicker.trailingAnchor constant:0.0],
[_overlay2.heightAnchor constraintEqualToConstant:38.0],
]
];
}
And, this only needs to be called from viewDidLoad (or wherever else you may find it appropriate). There is no need for it to be -- and in fact, it should not be -- called from viewDidLayoutSubviews or viewWillTransitionToSize.
As a side note -- if you are using remove and re-add to show and hide them, you'll also get a little better optimization if you add them once, and then set the .hidden property to YES or NO.

Determine height of UITableViewHeaderView dynamically

I'm running into a problem setting a tableHeaderView for a UITableView. I would like to have a detailsView be set as the tableHeaderView. The height of this detailsView will vary slightly and is not known immediately in view did load. The detailsView also has it's own subViews that have their own auto layout constraints. All auto layout is being done programatically. Let me post some sample code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:self.tableView];
self.tableView.tableHeaderView = self.detailsView;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self loadDetails];
}
-(void)loadDetails
{
//Omitted but does the following:
//1. Makes a call to the api to get details
//2. Once received sets the details to the detailsView
//3. Details could vary which influences detailView height size.
}
- (DetailsView *)detailsView
{
if(!_detailsView)
{
__weak DetailedViewController *_self = self;
_detailsView = [DetailsView new];
_detailsView.backgroundColor = [UIColor greenColor];
_detailsView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _detailsView;
}
-(void)updateViewConstraints
{
NSDictionary *views = #{
#"table" : self.tableView,
};
//Comment Detail View
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[table]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[table]-0-|" options:0 metrics:0 views:views]];
}
The problem with this approach is the tableHeaderView is pushed up to the top, meaning I can't scroll all the way through it properly. I'm not sure why this is happening. What I did as a test was replaced the detailsView with a UIImageView as follows.
-(UIImageView *)testImageView
{
if(!_testImageView)
{
NSString *filePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"blackImage"] ofType:#"jpg"];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
_testImageView = [[UIImageView alloc]initWithImage:image];
_testImageView.translatesAutoresizingMaskIntoConstraints = NO;
}
return _testImageView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view addSubview:self.tableView];
self.tableView.tableHeaderView = self.testImageView;
}
This code produces exactly what I need without knowing the height or any of the dimensions of the image.
As evidence by the photo, the header view is fully viewable and scrollable and I did not need to know any size of the image for this to work. I'd like to achieve the same thing with a UIView instead of the UIImageView for the tableHeaderView.
Important Notes:
The details view is not added as a sub view
The details view has not vertical height constraints or any constraints computed
The details view sub views have constraints computed programatically
Things I've Researched:
I've looked into instrinsicSize, sizeThatFits, anything that would allow a UIView to fill up the parent container view (tableHeaderView). I've tried various combinations of things with no success.
If anyone has a solution for how to solve this problem I'd appreciate it greatly!
(Let me know if this is not enough code to convey the context of the problem and I will post more.)
Use
systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
0) Init your header generically, being sure that the constraints hit all four sides of the headerView's frame.
1) Set the frame of the header view using
systemLayoutSizeFittingSize:UILayoutFittingCompressedSize
2) Assign your header view to the tableview's frame
For example:
self.headerView = [[YourCustomHeaderView alloc] initWithYourCustomObject:obj];
self.headerView.frame = CGRectMake(0,0, SCREEN_WIDTH, [self.headerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height);
self.tableView.tableHeaderView = self.headerView;

UIPageControl doesn't obey it's constraints inside UIScrollView

I have a UIScrollView and there are many views inside my scroll view. I am using autolayout and all my views are layed out in the same manner: Left and top spacing to the superview, width and height set. Everything scrolls just fine, however my page control stays whereever it is. It does not scroll with the other elements inside the scroll view. YES, I did check that the page control is inside the scroll view just like the other elements, and yes, I've quadruple-checked the constraints of the page control. It just won't scroll. What could be the problem? There are labels, another scroll view, text views, images views and they all scroll perfectly, it's just the page view that is problematic. Is there a bug with Xcode/iOS SDK, or am I missing something?
UPDATE: All the views inside my scroll view are inside a container view. Both the scroll view's and the container view's translatesAutoresizingMaskIntoConstraints property is set to NO. It's only the page control that doesn't obey it's contraints. Here is a screenshot from the Interface Builder:
I got it to work by:
1) Putting all views/controls inside a containerView and declaring it as a property.
2) Adding this code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
self.scrollView.contentSize = self.containerView.bounds.size;
}
- (void)viewDidLoad{
[super viewDidLoad];
//added with containerview from g8productions
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(36, 59, 900, 1200)];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:self.containerView];
[self.scrollView setContentSize:self.containerView.bounds.size];
}
Hope either of these solutions work for you!
I made something like that and I update the page control in the scrollview delegate methods:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
// If tag == 0 means that is the scrollview for gallery
if (scrollView.tag == 0)
{
self.pageControllGallery.currentPage = self.sv1ScrollViewGallery.contentOffset.x/320;
}
else
{
// Change the bottom label text
int page = (scrollView.contentOffset.x / 320);
[UIView animateWithDuration:0.3 animations:^{
self.labelBottom.alpha = 0.0f;
self.labelBottom.text = [self.arrayOfScrollviews objectAtIndex:page];
self.labelBottom.alpha = 1.0f;
}];
}
}
I calculate the page and set that page in PageControll.
Don't forget to set the Scrollview delegate in .h and in the element.
To size the scroll view’s frame with Auto Layout, constraints must either be explicit regarding the width and height of the scroll view, or the edges of the scroll view must be tied to views outside of its subtree.
https://developer.apple.com/library/ios/releasenotes/General/RN-iOSSDK-6_0/index.html
If you can dump Autolayout it would be best.
Check out that link, they've got a Pure AutoLayout example at the bottom.
Basically use this code pattern:
#interface ViewController () {
UIScrollView *scrollView;
UIImageView *imageView;
}
#end
#implementation ViewController
- (void)viewDidLoad{
[super viewDidLoad];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
[scrollView addSubview:imageView];
[self.view addSubview:scrollView];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
}

UIPageControl and UIScrollView during landscape orientation

I am having problems with UIPageControl and UIScrollView.
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [[NSArray alloc] initWithObjects:#"image1.jpg", #"image2.jpg", #"image3.jpg", nil];
for (int i = 0; i < [imageArray count]; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
[self.scrollView addSubview:imageView];
}
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [imageArray count], scrollView.frame.size.height);
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
pageControl.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
}
#pragma mark - UIScrollView Delegate
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
self.pageControl.currentPage = page;
}
The problem occurs when I change the orientation to landscape. image1.jpg shares the view with image2.jpg, which is not what I want.
But if I change the orientation back to portrait, the view is doing fine.
The problem is that the page size has changed (i.e. the width of the scrollview) but the width of the images hasn't changed.
One solution would be that when you rotate you re-do the computation that sizes the UIImageViews and computes the scrollview's contentSize. That is, move the for loop and assignment to scrollView.contentSize out of viewDidLoad and into a private method, say sizeImagesAndSetContentSize. Call that from willAnimateRotationToInterfaceOrientation:duration:
Something like the following:
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [[NSArray alloc] initWithObjects:#"image1.jpg", #"image2.jpg", #"image3.jpg", nil];
[self sizeImagesAndSetContentSize];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
pageControl.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
}
- (void) sizeImagesAndSetContentSize {
for (int i = 0; i < [imageArray count]; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
[self.scrollView addSubview:imageView];
}
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [imageArray count], scrollView.frame.size.height);
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
[self sizeImagesAndSetContentSize];
}
By the way, viewDidLoad is not the place to do geometry calculations -- like things that involve the frame of the scrollView because the geometry isn't set then. in viewDidLoad the sizes of things are the sizes that they were in the storyboard (or nib) and haven't been adjusted to the actual device and the actual orientation. If it works for you so far, it is because you start out in the orientation that the storyboard is laid out. Probably the best place to do that, and thus to call sizeImagesAndSetContentSize is in the method viewDidLayoutSubviews. In fact, if you move the call there, you won't need to explicitly call it on rotation because when rotation occurs the view lays out the subviews and viewDidLayoutSubviews will get called automatically. So I'd suggest:
- (void)viewDidLoad
{
[super viewDidLoad];
imageArray = [[NSArray alloc] initWithObjects:#"image1.jpg", #"image2.jpg", #"image3.jpg", nil];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
pageControl.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
}
- (void) sizeImagesAndSetContentSize {
for (int i = 0; i < [imageArray count]; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = [UIImage imageNamed:[imageArray objectAtIndex:i]];
[self.scrollView addSubview:imageView];
}
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [imageArray count], scrollView.frame.size.height);
}
- (void)viewDidLayoutSubviews {
[self sizeImagesAndSetContentSize];
}
Addendum:
ADDING TEXT TO THE IMAGE:
To add text on top of the image -- you could add a text element, like a label, "on top" of the scroll view. Consider the screenshot from an app below. In the top-level Cloud Slide Show View there is a full-sceen Scroll View and at the same hierarchy level but further down the list of subviews (and thus "on top" of the scrollview) are a label for a description, 3 buttons, and a UIPageControl. The label is set to resize on rotation via struts and springs (this runs on iOS 5) so you don't have to mess with it.
This application changes the description label to match the image that is currently displayed in the scrollview page -- so it has to detect page changes. This app does that in the scroll view delegate protocol method scrollViewDidEndDecelerating: where it decides which image is on-screen when scrolling stops and fills-in the correct description.
An alternative would be to put put a label on top of each UIImage, but that is messy which is why my app doesn't do that. You'd probably want a view to contain both the UIImage and a label so that repositioning the label would make sense. Probably create that in code... For me, too much trouble.
As an aside, I should say that using page mode in a scroll view is probably not the "best practice" way to do this anymore. I'm converting my app to use a UIPageViewController which should simplify the app. For instance, you don't have to do the resize-on-rotate thing since the individual child VCs of the UIPageViewControl can resize themselves automatically.

How to refresh the content of a UIView filled with photos

i have a modal view that is called from a table view. Whenever i call the modal view, i set a small view, inside the modal view, with some scrollable pictures inside it. The problem is that when i go back to the table view and select another row of the table, the old images are supposed to disappear and the new ones appear instead, but that is not happening. Seems like when i first load the modal view, it fills with the initial 2 pics and when i load it again it keeps adding the new pictures with the old ones..
Here is the code:
- (void)layoutScrollImages {
UIImageView *view = nil;
NSArray *subviews = [scrollView1 subviews];
//NSMutableArray *subviews = [scrollView1 subviews];
// reposition all image subviews in a horizontal serial fashion
CGFloat curXLoc = 0;
for (view in subviews)
{
if ([view isKindOfClass:[UIImageView class]] && view.tag > 0)
{
CGRect frame = view.frame;
frame.origin = CGPointMake(curXLoc, 0);
view.frame = frame;
curXLoc += (kScrollObjWidth);
}
}
// set the content size so it can be scrollable
[scrollView1 setContentSize:CGSizeMake(([self.nomeFotos count] * kScrollObjWidth), [scrollView1 bounds].size.height)];
}
and:
- (void)setScrollingImages {
//self.view.backgroundColor = [UIColor viewFlipsideBackgroundCol];
// 1. setup the scrollview for multiple images and add it to the view controller
//
// note: the following can be done in Interface Builder, but we show this in code for clarity
[scrollView1 setBackgroundColor:[UIColor blackColor]];
[scrollView1 setCanCancelContentTouches:NO];
scrollView1.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView1.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
scrollView1.scrollEnabled = YES;
// pagingEnabled property default is NO, if set the scroller will stop or snap at each photo
// if you want free-flowing scroll, don't set this property.
scrollView1.pagingEnabled = YES;
// load all the images from our bundle and add them to the scroll view
NSUInteger i;
for (i = 1; i <= [self.nomeFotos count]; i++)
{
NSString *imageName = [NSString stringWithFormat:[self.nomeFotos objectAtIndex:(i-1)]];
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
NSLog(#"%#", imageName);
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[scrollView1 addSubview:imageView];
}
[self layoutScrollImages]; // now place the photos in serial layout within the scrollview
}
i just figured what i wanted, a solution is to remove all the current subviews and then add the new views.. the code is as follow:
for (UIView* subView in [self.view.subviews]) {
[subView removeFromSuperview];
}
I put this code in my view will appear and everytime the view appears, it clears its subviews and the i call the method to add the new subviews (pictures).
Thanks

Resources