UIScrollView doesn't work with Autolayout (iOS 6) - ios

I made a few UIScrollView's in different views, they all worked without Autolayout.
I turned Autolayout on, because it was better for my app.
But since then, there's a big problem with my UIScrollView's:
No one is scrolling, they don't work.
Here's my code for a UIScrollView:
.m:
-(viewDidLoad) {
scrollerHome.contentSize = CGSizeMake(320, 1000);
scrollerHome.scrollEnabled = YES;
[self.view addSubview:scrollerHome];
scrollerHome.showsHorizontalScrollIndicator = false;
scrollerHome.showsVerticalScrollIndicator = false;
[super viewDidLoad];
}
.h:
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollerHome;
}
Do I have to add some code because I turned on Autolayout?

You should call [super viewDidLoad] before doing anything !

In autolayout, you do not set the contentSize manually. Autolayout works slightly differently with scrollviews, whereby the contentSize of the scroll view is dictated by the constraints of the scrollview's subviews.
If you're trying to force the contentSize to some large size (for example, you're implementing some infinite scroller), you can just add a subview of the appropriate size, e.g.:
UIView *containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:containerView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView);
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(1000)]|" options:0 metrics:nil views:views]];
But if you were trying to set the contentSize in anticipation of adding subviews, you generally don't have to do anything, such as the above snippet. Just add your subviews, provide their constraints, and autolayout will adjust the scroll view's contentSize automatically.
As mentioned above, with autolayout, you can just add the subviews to your scrollview (with their constraints), and the contentSize will be calculated automatically for you.
There is a trick here, though. You sometimes you want to size a subview based upon the dimensions of the screen. But the usual technique of using the | symbols won't work. For example, for an imageview1 inside a scrollview, the usual #"H:|[imageview1]|" won't set the imageview1 to be the width of the screen, but rather it will define the scroll view's contentSize to match the width of imageview1, but it says nothing about what the width of that image view should be!
So, it's useful to capture a reference to the scroll view's superview. That way, you can use something like #"H:|[imageview1(==superview)]|", which not only says "make the scroll view's contentSize equal to the width of imageview1", but also "define the width of imageview1 to be equal to the width of the scroll view's superview."
Thus, for example, to add three images in a paging scroll view, you might do something like:
UIImageView *imageview1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"_DSC0004.jpg"]];
imageview1.contentMode = UIViewContentModeScaleAspectFit;
imageview1.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:imageview1];
UIImageView *imageview2 = ... // configured similar to imageview1
UIImageView *imageview3 = ... // configured similar to imageview1
UIView *superview = self.scrollView.superview;
NSDictionary *views = NSDictionaryOfVariableBindings(imageview1, imageview2, imageview3, superview);
// not only define the image view's relation with their immediate scroll view,
// but also explicitly set the size in relation to the superview, too!
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageview1(==superview)][imageview2(==superview)][imageview3(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview1(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview2(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview3(==superview)]|" options:0 metrics:nil views:views]];
self.scrollView.pagingEnabled = YES;

From the Apple iOS 6.0 release notes:
"In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view."
You can find the full notes here and find the answer to your question in the section that I quoted from. They give code examples on how to use UIScrollView in a mixed Auto Layout environment.

Related

How to make an UIScrollView work with Autolayout and dynamic content? [duplicate]

This question already has answers here:
UITableView within UIScrollView using autolayout
(5 answers)
Closed 7 years ago.
I have this views hierarchy in a xib file:
UIView
UIScrollView
UIView
UIView
UITableView
UIButton
Let's call contentView the UIView that is the direct child of the UIScrollView. I've set its top, bottom, leading and trailing constraints to pin the scroll view. Then, since I'm populating the table view at runtime and I don't know its height beforehand, I set the scroll view's contentSize in code:
[self.scrollView setContentSize:CGSizeMake(self.contentView.frame.size.width, self.tableView.frame.size.height)];
But I don't make this work... what could I be missing?
AppsDev, check this video out it helped me a lot doing UIScrollView via storyboard
https://www.youtube.com/watch?v=UnQsFlMGDsI
Also never set the scrollView's contentSize as it has to be determined by scrollView on its own and that's why we have AutoLayout.
I believe we should have one tag for uiscrollview-autolayout
Don't set the content size manually. Instead,
Constrain your contentView's four edges to the edges of the scrollView.
Constrain your contentView's width to be equal to the scrollView's width. (This will prevent the content from being wider than the scrollView.)
Constrain the contentView's top and sides to the corresponding edges of the child view.
Constrain the contentView's sides and bottom to the sides and bottom of the tableView.
Now here's where it gets tricky: constrain the bottom of the child view to be equal to the top of the table view. However, unless you explicitly set a height constraint on the child view, you'll get a layout error that the height of the scrollView's contents will be ambiguous. To get around this, you can set the child's placeholder height to make Interface Builder happy, but then you'll also have to set its height somewhere at runtime.
Now you should be set. The scrollView can now calculate the full height and width of its contents by examining the constraint hierarchy, and you don't have to set its content height manually.
I finally managed to make this work by following the #Sana answer and also this post to be able to scroll the table view content.
Thanks u all for replying.
Just for example:
UIView *containerView = [[UIView alloc] init];
UIScrollView *scrollView = [[UIScrollView alloc] init];
[containerView addSubview:scrollView];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(containerView, scrollView);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
Use this code My code help you.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
[self.scrollView setContentSize:CGSizeMake(self.contentView.frame.size.width, self.tableView.frame.size.height)];
}

Auto layout error Unable to simultaneously satisfy constraints with UIScrollView

I have a UIScrollView that is to show UIImageViews. The ImageViews are programmatically inserted at runtime based on how many images are saved by the user previously. I get the error below:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one
you don't want. Try this: (1) look at each constraint and try to
figure out which you don't expect; (2) find the code that added the
unwanted constraint or constraints and fix it. (Note: If you're seeing
NSAutoresizingMaskLayoutConstraints that you don't understand, refer
to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
"<NSLayoutConstraint:0x17029cd90 H:[UIView:0x15cd31e70]-(0)-| (Names: '|':UIScrollView:0x15cd326b0 )>",
"<NSLayoutConstraint:0x17029d060 UIScrollView:0x15cd326b0.trailingMargin == UIView:0x15cd31e70.trailing>"
I do the basic Autolayout thing where the scrollview is pinned to all four sides at 0 points. I then add a contentView as a subview (plain UIView) of the UIScrollView which is also pinned to all four sides at 0 points.
EDIT Storyboard constraints image
I give the contentView a width in code like so:
CGSize pagesScrollViewSize = self.scrollView.frame.size;
NSDictionary *views = #{ #"contentView" : self.contentView};
NSDictionary *metrics = #{ #"width" : #(pagesScrollViewSize.width * self.mutableArray.count) };
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[contentView(width)]-|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
[self loadVisiblePages];
The UIImageViews are added like so where UIImageViews are added based on the number of pages set when the segue to the ViewController occurs.
-(void)loadVisiblePages{
CGRect frame = self.scrollView.bounds;
frame.origin.x = frame.size.width * self.page;
frame.origin.y = 0.0f;
ImageForArchiving *newImageObject = (ImageForArchiving*)self.mutableArray[page];
UIImage *imageForNewPageView = newImageObject.image;
UIImageView *newPageView = [[UIImageView alloc] initWithFrame:frame];
[newPageView setTranslatesAutoresizingMaskIntoConstraints:YES];
newPageView.contentMode = UIViewContentModeScaleAspectFit;
newPageView.image = imageForNewPageView;
[self.scrollView addSubview:newPageView];
[self.pageViews replaceObjectAtIndex:page withObject:newPageView];
}
}
Additionally, when I scroll the UIScrollView the images displayed change size erratically on rotation. I think that this is just a consequence of the above warning and the fact that I haven't layed out the UIImageViews yet. What does the above warning mean in this context and how do I fix it?
It seems you have pinned trailingMargin of scrollView to contentView.trailing.
Change scrollView.Trailing Margin to scrollView.Trailing for this constraint
You can do this in the activity inspector in the storyboard after selecting your constraint.
Alternatively, clear all constraints on your contentView. Then while adding pinning constraints again uncheck Constrain to margins and set all constants 0.
AND
Change this line in your code
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[contentView(width)]-|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
with this:
NSArray *constraints;
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView(width)]|" options:0 metrics:metrics views:views];
[self.scrollView addConstraints:constraints];
Using #"H:|-[contentView(width)]-|" in visual format means pinning your contentView to the superView's margins and adds a space of 8 pts between the superView and subView. In your storyboard constraints you had set up constraints with the Trailing and Leading edges of the UIScrollView, while in the programmatically added constraint you had used Trailing Margin and Leading Margin (kind of asking the contentView to maintain an 8 pt. padding). Hence, the conflict.
Check Visual Format syntax here.

Superview not increasing in height based on the subviews constraint

I have a scrollview and a separate UIView where I placed a series of textFields and labels with constraints which fully occupies the top and bottom. I'm trying to adjust the UIView's height based on its subview constraints but it won't. What is happening is that the view keeps its height and force other textfields to collapse or shrink thus breaking the constraints.
Details
Each subview priority values :
compression = 750
hugging = 250
UIView priority values:
compression = 249
hugging = 749 Set to be lower than the rest.
Most of the textfields has aspect ratio constraint. This causes the field to adjust.
Each subview has vertical/top/bottom spacing between each other. The top and bottom elements has top and bottom constraints to the view as well.
What's on my code:
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
/* I had to adjust the UIView's width to fill the entire self.view.*/
if(![contentView isDescendantOfView:detailsScrollView]){
CGRect r = contentView.frame;
r.size.width = self.view.frame.size.width;
contentView.frame = r;
[detailsScrollView addSubview:contentView];
}
}
Screenshots
The view
This is what currently happens. In this instance it forces the email field to shrink. If I place a height value on it, it does not shrink but the layout engine finds another element to break
Edit:
Solved
Maybe I just needed some break to freshen up a bit. I did tried using constraints before but got no luck. However thanks to the suggestion I went back setting the constraints instead of setting the frame on this one and got it finally working.
Solution:
-(void)viewDidLoad{
[detailsScrollView addSubview:contentView];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
[detailsScrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(contentView,detailsScrollView);
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing metrics:nil
views:viewsDictionary];
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[contentView]-0-|"
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:viewsDictionary];
NSArray *widthConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentView(==detailsScrollView)]-0-|" options:0 metrics:nil views:viewsDictionary];
}
When you use interface builder to deal with the UIScrollView and its child UIView. usually a top, bottom, left and equal width constraints are set between the UIScrollView and its child which is the contentView in your case.
Without those constraints the other option is to set the content size of the UIScrollView. which was the way of using the UIScrollView before introducing constraints.
So, 1. you should add those constraints programmatically.
By using the constraints, the views frame is no longer needed to resize the views.
So, 2. remove frame setting for your content view.
I am not so happy with the way you set the frame in the viewDidLayoutMethod. if I am going to do that here I would take the frame setting out of the if statement.
The code would be as follow with no if statement:
[detailsScrollView addSubview:contentView];
// then set the constraints here after adding the subview.
Put this code anywhere but not inside your viewDidLayoutSubviews method. it will be a bigger problem than setting the frame in there inside if statement.
Note: Originally, if you are going to set frame in the viewDidLayoutSubviews
method. you should do it for all cases. for example for the if case
and the else case. because, next time this method is going to be
called the views will respond to the constraint. and lose its frame.
Another observation: if you want the view to response to its subviews constraint why you need to set the frame for it? right?
After adding the constraint you may need to call the method constraintNeedsUpdate or another related method.

Adding a dynamically sized view (height) to a UIScrollView with Auto Layout

Here is my structure of views for this detail view (blogging application, I want to view the entire post which has dynamic height inside of a scrollview):
UIView
-UIScrollView
-SinglePostView (custom view)
-Title
-Subtitle
-Content
Normally to add a 'single post view' to a UIView I simply instantiate it, feed it my Post object and then add a single constraint that pins the width of the singlePostView to the superview, at which point things get laid out nicely. However when I try to add it to a scroll view, it doesn't show up nor does it scroll.
UIScrollView *scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
NOBSinglePostView *singlePost = [[NOBSinglePostView alloc] initWithPost:self.post];
[scrollView addSubview:singlePost];
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,singlePost);
[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:|[singlePost]|" options:0 metrics: 0 views:viewsDictionary]];
In the structure you are presenting, using AutoLayout, the contentSize of your UIScrollView is driven by the intrinsicContentSize of its subviews, here your SinglePostView.
The problem is, SinglePostView being a subclass of UIView, its intrinsicContentSize is always CGSizeZero when considered by itself. What you need to do is make the intrinsicContentSize of your SinglePostView depend on the intrinsicContentSize of its subviews.
Then, because the subviews of your SinglePostView are UILabels, and because a UILabel's intrinsicContentSize is the smallest size it needs to display its content, your SinglePostView's intrinsicContentSize will be equal to the sum of its subviews intrinsicContentSizes, that is the total size needed to display the content of all three of your labels.
Here is how to do it.
Step 1: Removing all automatically set constraints
First, as you partially did, you need to remove all constraints automatically set by the system.
Assuming you don't have any constraints set in your storyboard or XIB (or you don't even have one of these), just do:
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
contentLabel.translatesAutoresizingMaskIntoConstraints = NO;
Now you have a clear slate and you can start setting your own constraints.
Step 2: Constraining the scrollView
First, let's create, as you did, the views references dictionary for AutoLayout to use:
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, singlePost, titleLabel, subtitleLabel, contentLabel);
Then, also as you already did, let's constrain the scroll view to be the size of its superview:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
Step 3: Constraining the SinglePostView to push the scrollView's contentSize
For this step to be clear, you have to understand that every constraints set between a UIScrollView and its subviews will actually change the contentSize of the UIScrollView, not its actual bounds. For Example, if you constrain a UIImageView to the borders of its parent UIScrollView and then put an image twice the size of the UIScrollView inside the UIImageView, your image won't get shrunk, its the UIImageView that will take the size of its image and become scrollable inside the UIScrollView.
So here is what you have to set here:
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:singlePost
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
First two constraints are pretty obvious. The third one, however, is here because, for your UILabels to display their content properly and still be readable, you will probably want them to be multilined and the scrolling to be vertical, not horizontal. That's why you set your SinglePostView's width to be the same as your scrollView's. This way, you prevent your scrollView's contentSize.width to be anything more than its bounds.width.
Step 4: Constraining your UILabels to "push" the bounds of your SinglePostView
Fourth and final step, you now need to set constraints on your SinglePostView's subviews, so that it gets an intrinsicContentSize from them.
Here is how you do it (simplest implementation, no margins, one label after the other vertically):
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[titleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[subtitleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[titleLabel]-0-[subtitleLabel]-[contentLabel]-0-|" options:0 metrics:0 views:views]];
And with that you should be done.
One last advice, you should definitely look into UIStoryboard to do these kinds of things. It's a lot simpler and more visual.
Hope this helps,
P.S.: If you want, I can take some time and push a project using both UIStoryboard and the Visual Format Language on Github. Just tell me if you would need one.
Good luck.
in auto layout
frame of scrollview is decided by constraints between scrollview and superview of scrollview.
contentSize of scrollview is decided by constraints between scrollview and subview of scrollview.
you should set the size of singlePostView. ScrollView calculate contentSize from it. (you need to add size constraints explicitly)
CGFloat heightOfSinglePostView = calculate..
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[singlePost(heightOfSinglePostView)]|" options:0 metrics: 0 views:viewsDictionary]];

iOS: Resizing UIImageView with AutoLayout

I am new to AutoLayout and struggling with it a bit. I have a UIImageView which is part of an UIView. When the app is running on a 3.5 inch device, I want the UIImageView to resize according to the change of the screen size.
I am not sure how to achieve this. From what I have seen so far, you can only set a fixed width and height of the views, is it possible to make them dynamically resize ? In other words, how can the UIImageView resize accordingly to the super view's height ?
Choose your ImageView in Interface Biulder.
Press pin button.
Set the constraints.
Example
This code will size the UIImageView according to its superview's width and height.
UIImageView *myImageView = [[UIImageView alloc] initWithImage:theImage];
[self.view addSubview:myImageView];
[myImageView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSArray *imageViewConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[myImageView]|" options:0 metrics:nil views:#{#"myImageView": myImageView}];
[self.view addConstraints:imageViewConstraints];
imageViewConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myImageView]|" options:0 metrics:nil views:#{#"myImageView": myImageView}];
[self.view addConstraints:imageViewConstraints];
Me too faced same problem.It may help someone,
If you are designing in InterfaceBuilder or Storyboard just pin all the edges to superview (i.e)Leading Space,Trailing Space,Top Space and Bottom Spoace to UIView.
That's it
select the imageView. Now go and add a pin with bottom spacing from super view.

Resources