UIScrollView wrong offset with Auto Layout - ios

I have a fairly simple view configuration:
A UIViewController, with a child UIScrollView and a UIImageView in this UIScrollView.
I set the UIImageView with a height sufficient to break out of the visible area (ie. higher to 1024pt), and set the Bottom space to superview constraint of my UIImageView to a fixed positive value (20 for example).
The whole setup works as expected, the image scrolls nicely in its parent.
Except when the view is scrolled (the effect is more visible if you scrolled to the bottom of the view), then disappear, and appear again (you switched to another view and came back) the scrolling value is restored, but the content of the scroll view is moved to the outside top part of its parent view.
This is not simple to explain, I'll try to draw it:
If you want to test/view the source (or the storyboard, I did not edit a single line of code). I put a little demo on my github: https://github.com/guillaume-algis/iOSAutoLayoutScrollView
I did read the iOS 6 changelog and the explanation on this particular topic, and think this is the correct implementation of the second option (pure auto layout), but in this case why is the UIScrollView behaving so erratically ? Am I missing something ?
EDIT: This is the exact same issue as #12580434 uiscrollview-autolayout-issue. The answers are just workarounds, as anyone found a proper way to fix this or is this a iOS bug ?
EDIT 2: I found another workaround, which keep the scroll position in the same state the user left it (this is an improvement over 12580434's accepted answer):
#interface GAViewController ()
#property CGPoint tempContentOffset;
#end
#implementation GAViewController
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.tempContentOffset = self.mainScrollView.contentOffset;
self.scrollView.contentOffset = CGPointZero;
}
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.scrollView.contentOffset = self.tempContentOffset;
}
This basically save the offset in viewWillAppear, reset it to the origin, and then restore the value in viewDidAppear. The problem seems to occur between these two calls, but I can't find its origin.

Yeah, something strange happened with UIScrollView in pure autolayout environment. Re-reading the iOS SDK 6.0 release notes for the twentieth time I found that:
Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.
Solution
Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.
As IB does not allow us set up constraints between the imageView and a view outside the scroll view’s subtree, such as the scroll view’s superview then I've done it in code.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view removeConstraints:[self.view constraints]];
[self.scrollView removeConstraints:[self.scrollView constraints]];
[self.imageView removeConstraints:[self.imageView constraints]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_scrollView]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_scrollView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[_imageView(700)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imageView(1500)]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_imageView)]];
}
And vau! It works!

The edit didn't work for me. But this worked:
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
self.tempContentOffset = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointZero;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.scrollView.contentOffset = self.tempContentOffset;
}

For me I went to the IB clicked my view controller that contains the scroll view. Then I went to Attribute Inspector -> View Controller -> Extend Edges -> Uncheck "Under Top Bars" and "Under Bottom Bars".

Simple solution found, Just put
[self setAutomaticallyAdjustsScrollViewInsets:NO];
in your ViewControllers viewDidLoad method
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self setAutomaticallyAdjustsScrollViewInsets:NO];
}

I had a similar problem using a UIScrollView in a UIViewController with a top extra space that was not visible in Storyboard. The solution for me was to uncheck the "Adjust Scroll View Insets" on the ViewController storyboard properties : see answer Extra Space (inset) in Scroll View at run time

Add a global property contentOffset and save the current contentOffset in viewDidDisappear.
Once you return the method viewDidLayoutSubviews will be called and you can set your original contentOffset.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.scrollView setContentOffset:self.contentOffset animated:FALSE];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.contentOffset = self.scrollView.contentOffset;
[self.scrollView setContentOffset:CGPointMake(0, 0) animated:FALSE];
}

Looks like the problem solved with the dispatch_async during the viewWillAppear:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGPoint originalContentOffset = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointZero;
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollView.contentOffset = originalContentOffset;
});
}

Related

UIScrollView got freeze when Back button pressed?

I am having a (UIView and UICollectionView in a UIScrollView) as 1stVC. I have to scroll both at the same time for which I already unable the collection view scrolling. So first time When I launch that screen I am able to scroll the whole view, But when I push to (next ViewController) 2ndVC and then press Back Button to 1stVC my scroll view is not preforming it got Freeze.
Tried this Method:-
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
dispatch_async(dispatch_get_main_queue(), ^ {
CGRect contentRect = CGRectZero;
for(UIView *view in self.scrollView.subviews)
contentRect = CGRectUnion(contentRect,view.frame);
self.scrollView.contentSize = contentRect.size;
});
}
Tried this Method:-
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
[_scrollView setContentSize:CGSizeMake(_scrollView.frame.size.width, _scrollView.frame.size.height)];
}
And For Getting Dynamic Height of the Collection view I have Done This
CGFloat height = _collectionCompass.contentSize.height;
Which I Have provided to ScollView ContentSize.
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width, height);
Please help me.
What I really suggest it's to remove scroll view and do all stuff in collection view, since with scroll view you're missing some of benefits (e.g. cells reusing). Also collection view in scroll view it's a bad practice.
From Apple style guide:
Don’t place a scroll view inside of another scroll view. Doing so creates an unpredictable interface that’s difficult to control.
If your UI as list, you can add view as header of collection view or as another cell. With this approach you can remove the code from viewDidLayoutSubviews

Autolayout is changing UIScrollView's contentOffset on rotation

To experiment with autolayout and uiscrollview's I have been using this example
which I have edited to include 2 views in the scroll view, I have setup the autolayout constraints to position the views horizontally adjacent with their size set to fill the scroll view frame.
UIView *beeView = [[[NSBundle mainBundle] loadNibNamed:#"BeeView" owner:nil options:nil] firstObject];
beeView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:beeView];
UIView *beeView2 = [[[NSBundle mainBundle] loadNibNamed:#"BeeView" owner:nil options:nil] firstObject];
beeView2.backgroundColor= [UIColor orangeColor];
beeView2.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:beeView2];
NSDictionary *views = #{#"beeView":beeView,#"beeView2":beeView2, #"scrollView":self.scrollView};
NSDictionary *metrics = #{#"height" : #200};
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[beeView(==scrollView)][beeView2(==beeView)]|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:metrics views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[beeView(==scrollView)]|" options:kNilOptions metrics:metrics views:views]];
which nicely produces what I intended.
However, if the scroll view's contentOffset is nonzero and the device is rotated from portrait to landscape, the content offset of the scroll view is automatically set to 32px. (see screenshot)
I have tried saving contentOffset and setting it to this saved value when scrollViewDidEndDecelerating: is called which works but is ugly as the scroll view scrolls to a 32px offset and then back to where I want it to be.
How do I control the scroll view's contentOffset? Are the autolayout constraints wrong? Are there extra constraints I can add to control the contentOffset when resizing the view?
Where the 32px comes from? Is it related to your left and right scrollView margin?
Does it keep the wrong offset every time you change page ? If that the case, you should look at your scrollView's contentInsets values.
Otherwise, what I do to manage rotation on scrollView with paging is observing the scrollView's contentSize:
First, when you load the view, add the observer:
[self.scrollView addObserver:self forKeyPath:NSStringFromSelector(#selector(contentSize)) options:0 context:nil];
Then, when the contentSize value change, adjust the contentOffset:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == self.scrollView && [keyPath isEqualToString:NSStringFromSelector(#selector(contentSize))]) {
//Note that you should track your page index
self.scrollView.contentOffset = CGPointMake(self.pageIndex * self.scrollView.bounds.size.width, self.scrollView.contentOffset.y);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Finally, remove the observer when you unload the scrollView:
[self.scrollView removeObserver:self forKeyPath:NSStringFromSelector(#selector(contentSize)) context:nil];
After referring back to a few previous posts: one & two. It seems you might find your solution following one of these steps:
Programmatically : If your UIScrollViews' parent VC is not directly on the Nav Stack -
~Edit~
// Without a Navigation Controller
self.automaticallyAdjustsScrollViewInsets = NO;
// With a Navigation Controller
self.parentViewController.automaticallyAdjustsScrollViewInsets = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
Interface Builder : Attributes Inspector -> Uncheck Adjust Scroll View Insets in the Layout Properties.
IF Still Unsuccessful : Within the presented View Controller's ViewWillLayoutSubviews Method try setting each of the following UIScrollView properties:
self.scrollView.contentOffset = CGPointZero;
self.scrollView.contentInset = UIEdgeInsetsZero;
I am guessing a combination of option 1 and 2 will work, depending on how your Navigation Stack is structured.
For iOS 11.0 and greater, the following method solves the issue for me:
if (#available(iOS 11.0, *))
{
self.myScrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}
I know this is an old question, but in case it is of use to anyone - I have created a subclass of UIScrollView called LMPageView that automatically applies the necessary layout constraints and adjusts the content offset on rotation. The class is available as part of the MarkupKit project on GitHub. Example usage (Swift):
// Add 3 page views
pageView.addPage(view1)
pageView.addPage(view2)
pageView.addPage(view3)
// Show the 3rd page
pageView.currentPage = 2
An additional example that uses markup to initialize a page view can be found here:
PageViewController.swift
PageViewController.xml

How to adjust height of table view programmatically in viewdidload

How can I adjust my tableview height programmatically in viewdidload? actually I have a view in my app I want that whe view will load then uivew will be hide ang tableview height will be increase? How it can be done? I had added these lines in viewdidload but nothing happens?
- (void)viewDidLoad {
[super viewDidLoad];
CGRect tableFrame = [tableListView frame];
tableFrame.size.height = 127;
[tableListView setFrame:tableFrame];
}
Below is my screen shot I want to hide that greycolourview and want to increase my tableview height.
Give this a shot:
1.
This should be your constraints on the tableView:
2.
//declare a constraint outlet and connect it
IBOutlet NSLayoutConstraint *constraintGrayViewBottom;
3.
This is the bottom constraint of that gray view i.e: constraintGrayViewBottom.
Select the bottom constraint as shown and on the right, click-drag the referencing outlet connection to the viewController and connect it to constraintGrayViewBottom.
4.
Handle the constraint changes as example:
-(void)viewDidAppear:(BOOL)animated {
constraintGrayViewBottom.constant = -60;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}];
}

UIScrollView Zoom Does Not Work With Autolayout

Zooming with UIScrollView using a strictly autolayout environment does not seem to work.
This is especially frustrating because the iOS 6 release notes certainly lead me to believe it should when the wrote about a "Pure Auto Layout approach" here http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html
I looked the the WWDC 2012 slides for sessions 202, 228, and 232 and didn't see an answer for this.
The only question I've seen on the internet specifically for this issue is UIScrollView zooming with Auto Layout, but it doesn't provide code of the problem and there is no answer.
This user https://stackoverflow.com/users/341994/matt has given many great responses to UIScrollView autolayout questions and even linked to code on git hub, but I haven't been able to find anything that answers this issue there.
I have attempted to boil this issue down to the absolute minimum to make it clear.
I created a new single view application with a storyboard, and made no changes in the interface builder.
I added a large picture file to the project "pic.jpg".
SVFViewController.h
#import <UIKit/UIKit.h>
#interface SVFViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic) UIImageView *imageViewPointer;
#end
SVFViewController.m
#import "SVFViewController.h"
#interface SVFViewController ()
#end
#implementation SVFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIScrollView *scrollView = [[UIScrollView alloc] init];
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImage:[UIImage imageNamed:#"pic.jpg"]];
[self.view addSubview:scrollView];
[scrollView addSubview:imageView];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageViewPointer = imageView;
scrollView.maximumZoomScale = 2;
scrollView.minimumZoomScale = .5;
scrollView.delegate = self;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
NSLog(#"Current views dictionary: %#", viewsDictionary);
[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]];
}
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.imageViewPointer;
}
#end
Notice I made a particular effort to make this as much like the sample code provided in the iOS 6 release notes, just doing the bare minimum to implement zooming.
So, the problem?
When you run this application and pan around in the scroll view, everything is good. But when you zoom the problem is obvious, the image flickers back and forth, and the placement of the image within the scroll view gets more wrong with every zoom.
It looks like there is battle going on for the content offset of the imageView, it seems it is being set to different values by two different things with every "zoom". (an NSLog of the content offset property of the imageView appears to confirm this).
What am I doing wrong here? Does anyone know how to property implement zooming within a UIScrollView in an purely autolayout environment. Is there an example of this anywhere out there?
Please help.
Once again, re-reading the iOS SDK 6.0 release notes I found that:
Note that you can make a subview of the scroll view appear to float (not scroll) over the other scrolling content by creating constraints between the view and a view outside the scroll view’s subtree, such as the scroll view’s superview.
Solution
Connect your subview to the outer view. In another words, to the view in which scrollview is embedded.
And applying constraints in following way I've got it work:
[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]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView(width)]" options:0 metrics:#{#"width":#(self.imageViewPointer.image.size.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(height)]" options:0 metrics:#{#"height":#(self.imageViewPointer.image.size.height)} views:viewsDictionary]];
The issues that occurs is the changing of location of the imageview during the zoom process. The origin location of the imageview will change to be a negative value during the zoom. I believe this is why the jerky movement occurs. As well, after the zoom is complete the imageView is still in the wrong location meaning that scrolls will appear to be offset.
If you implement -(void) scrollViewDidZoom:(UIScrollView *)scrollView and log the frame of the UIImageView during this time you can see its origin changing.
I ended up making things work out by implementing a strategy like this
And in addition changing the frame of the contentView while zooming
-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
CGRect cFrame = self.contentView.frame;
cFrame.origin = CGPointZero;
self.contentView.frame = cFrame;
}
These solutions all kinda work. Here is what I did, no hacks or subclasses required, with this setup:
[view]
[scrollView]
[container]
[imageView]
[imageView2]
In IB, hook up top, leading, bottom and trailing of scrollView to view.
Hook up top, leading, bottom and trailing of container to scrollView.
Hook up center-x and center-y of container to center-x and center-y of scrollView and mark it as remove on build time. This is only needed to silence the warnings in IB.
Implement viewForZoomingInScrollView: in the view controller, which should be scrollView's delegate, and from that method return container.
When setting the imageView's image, determine minimum zoom scale (as right now it will be displayed at the native size) and set it:
CGSize mySize = self.view.bounds.size;
CGSize imgSize = _imageView.image.size;
CGFloat scale = fminf(mySize.width / imgSize.width,
mySize.height / imgSize.height);
_scrollView.minimumZoomScale = scale;
_scrollView.zoomScale = scale;
_scrollView.maximumZoomScale = 4 * scale;
This works for me, upon setting the image zooms the scroll view to show the entire image and allows to zoom in to 4x the initial size.
Let say you have in storyboard "UIImageView" inside "UIScrollView" inside "UIView".
Link all constraints in "UIScrollView" with the view controller + the two constraints in UIView (Horizontal Space - Scroll View - View & Horizontal Space - Scroll View - View).
set the view controller AS delegate for the "UIScrollView".
Then implement this code:
#interface VC () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray* constraints;
#end
#implementation FamilyTreeImageVC
- (void)viewDidLoad {
[super viewDidLoad];
[self.scrollView removeConstraints:self.constraints];
}
- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return self.imageView;
}
-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
CGRect cFrame = self.imageView.frame;
cFrame.origin = CGPointZero;
self.imageView.frame = cFrame;
}
I had the same problem when trying to implement zoom from a storyboarded project using only scrollView.
I fixed it by adding a separate pinch gesture recogniser. I just dragged it from the toolbox onto my scene. Then I connected it to an action I called "doPinch" that implements the zoom. I connected it to an outlet I called "pinchRecognizer" so that I could access its scale property. This seems to override the built in zoom of the scrollView and the jumpiness disappears. Maybe it does not make the same mistake with origins, or handles that more gracefully. It is very little work on top of the layout in IB.
As soon as you introduce the pinch gesture recogniser to the scene you do need both the action and viewForZoomingInScrollView methods. Miss out either and the zooming stops working.
The code in my view controller is this:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
This very basic implementation does have a side effect: when you come back to a zoomed image and zoom some more the value of scale is 1.0f so it jumps back to the original scale.
You can sort this out by introducing a property "currentScale" to track the scale and set the pinch gesture recogniser scale when you start zooming again. You need to use the state property of the gesture recogniser:
- (IBAction)doPinch:(id)sender
{
NSLog(#"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
NSLog(#"Gesture recognizer state is: %d", self.pinchRecognizer.state);
switch (self.pinchRecognizer.state)
{
case 1:
NSLog(#"Zoom begins, with current scale set to: %f", self.currentScale);
[self.pinchRecognizer setScale:self.currentScale];
break;
case 3:
NSLog(#"Zoom ends, with pinch recognizer scale set to: %f", self.pinchRecognizer.scale);
self.currentScale = self.pinchRecognizer.scale;
default:
break;
}
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}
So this is what I managed to work out.
Here's the original with my changes:
#interface ScrollViewZoomTestViewController () <UIScrollViewDelegate>
#property (nonatomic, strong) UIImageView* imageViewPointer;
// These properties are new
#property (nonatomic, strong) NSMutableArray* imageViewConstraints;
#property (nonatomic) BOOL imageViewConstraintsNeedUpdating;
#property (nonatomic, strong) UIScrollView* scrollViewPointer;
#end
#implementation ScrollViewZoomTestViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIScrollView *scrollView = [[UIScrollView alloc] init];
UIImageView *imageView = [[UIImageView alloc] init];
[imageView setImage:[UIImage imageNamed:#"pic.jpg"]];
[self.view addSubview:scrollView];
[scrollView addSubview:imageView];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.translatesAutoresizingMaskIntoConstraints = NO;
self.imageViewPointer = imageView;
// New
self.scrollViewPointer = scrollView;
scrollView.maximumZoomScale = 2;
scrollView.minimumZoomScale = .5;
scrollView.delegate = self;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView, imageView);
NSLog(#"Current views dictionary: %#", viewsDictionary);
[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]];
// Saving the image view width & height constraints
self.imageViewConstraints = [[NSMutableArray alloc] init];
// Constrain the image view to be the same width & height of the scroll view
[_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
[_imageViewConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(scrollView)]|" options:0 metrics: 0 views:viewsDictionary]];
// Add the image view constraints to the VIEW, not the scroll view
[self.view addConstraints:_imageViewConstraints];
// Flag
self.imageViewConstraintsNeedUpdating = YES;
}
So to recap here, I'm adding all of the constraints to self.view, saving the constraints set on the UIImageView in a NSMutableArray property, and setting a flag that the UIImageView constraints need updating.
These initial constraints on UIImageView work to set it up to start with. It will be the same width & height as the UIScrollView. However, this WON'T allow us to zoom the image view. It will keep it the same width / height as the scroll view. Not what we want. That's why I'm saving the constraints and setting the flag. We'll take care of that in a minute.
Now, set the view for zooming:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageViewPointer;
}
Ok, so now we need to actually allow us to zoom. I'm removing the initial UIImageView constraints and adding some new ones, this time constraining to the UIScrollView's contentSize width & height:
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if(_imageViewConstraintsNeedUpdating)
{
// Remove the previous image view constraints
[self.view removeConstraints:_imageViewConstraints];
// Replace them with new ones, this time constraining against the `width` & `height` of the scroll view's content, not the scroll view itself
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_scrollViewPointer, _imageViewPointer);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imageViewPointer(width)]|" options:0 metrics:#{#"width" : #(_scrollViewPointer.contentSize.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imageViewPointer(height)]|" options:0 metrics:#{#"height" : #(_scrollViewPointer.contentSize.height)} views:viewsDictionary]];
self.imageViewConstraintsNeedUpdating = NO;
}
}
#end
We can't set the constraints up like this in -viewDidLoad because the image hasn't been rendered into the UIImageView yet, so UIScrollView's contentSize will be {0,0}.
This seems pretty hacky to me, but it does work, it does use pure Auto Layout, and I can't find a better way to do it. Seems to me like Apple needs to provide a better way to zoom content in a UIScrollView AND use Auto Layout constraints.

Redraw subviews after UIScrollView zoom

I've got a UIScrollView with a child UIView (CATiledLayer) - then I have a bunch more child views of that view (some of which are UITextViews)
After zooming everything is all fuzzy.
I have read various articles on the subject and they all seem to indicate that I must handle scrollViewDidEndZooming then 'do some transform stuff, mess with the frame and tweak the content offset'. Please can someone put me out of my misery and explain how this is meant to work.
Thanks in advance...
I had a similar problem where I needed zooming with text. Mine wasn't using the CATiledLayer, so this may or may not work for you. I also wasn't using ARC, so if you are, you'll have to adjust that as well.
The solution I came up with was to set the UIScrollViewDelegate methods as follows this:
// Return the view that you want to zoom. My UIView is named contentView.
-(UIView*) viewForZoomingInScrollView:(UIScrollView*)scrollView {
return self.contentView;
}
// Recursively find all views that need scaled to prevent blurry text
-(NSArray*)findAllViewsToScale:(UIView*)parentView {
NSMutableArray* views = [[[NSMutableArray alloc] init] autorelease];
for(id view in parentView.subviews) {
// You will want to check for UITextView here. I only needed labels.
if([view isKindOfClass:[UILabel class]]) {
[views addObject:view];
} else if ([view respondsToSelector:#selector(subviews)]) {
[views addObjectsFromArray:[self findAllViewsToScale:view]];
}
}
return views;
}
// Scale views when finished zooming
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat contentScale = scale * [UIScreen mainScreen].scale; // Handle retina
NSArray* labels = [self findAllViewsToScale:self.contentView];
for(UIView* view in labels) {
view.contentScaleFactor = contentScale;
}
}
I hit the same problem and the code above didn't work for my case. Then I followed the documentation:
If you intend to support zoom in your scroll view, the most common technique is to use a single subview that encompasses the entire contentSize of the scroll view and then add additional subviews to that view. This allows you to specify the single ‘collection’ content view as the view to zoom, and all its subviews will zoom according to its state.
"Adding Subviews" section of https://developer.apple.com/library/ios/#documentation/WindowsViews/Conceptual/UIScrollView_pg/CreatingBasicScrollViews/CreatingBasicScrollViews.html
I simply created an empty view and added my CATiledLayer and all other subviews to that empty view. Then added that empty view as the only child view of the scroll view. And, it worked like a charm. Something along these lines:
- (void)viewDidLoad
{
_containerView = [[UIView alloc] init];
[_containerView addSubView:_yourTiledView];
for (UIView* subview in _yourSubviews) {
[_containerView addSubView:subview];
}
[_scrollView addSubView:_containerView];
}
-(UIView*) viewForZoomingInScrollView:(UIScrollView*)scrollView {
return _containerView;
}

Resources