Getting UIScrollView to work with AutoLayout - ios

This seems to be a pretty common question but no matter which piece of code or change I make I cannot get it to work. I have a very simple example that I'm working on.
Storyboard is set to 4" portrait. UIScrollView 320px wide x 440px high. It contains a UIView which is 320px wide by 900px high. Application is locked to portrait only but needs to work on both 3.5" and 4" screens.
What do I need to do to get the UIView to scroll vertically within the UIScrollView? Do I need to set a contraint? What should Clip Subviews and Autoresize Subviews be set to and does it make a difference? What should View Mode be set to and does it make a difference? Do I need to programmatically reset the ContentSize of the UIScrollView? If so is this done on the ViewDidLoad event?
I've tried adding vertical contraints to the UIView, the bottom element on the UIView and the UIScrollView itself. I've programmatically set the content size to match the UIView.
I've built UI's in all sorts of other tools and this is just driving me insane for something so simple. If I turn off autolayout it works fine but that's not the solution I'm looking for.

Ended up getting this answer to work.
https://stackoverflow.com/a/16029013/1342779
Specifically my code in the viewDidLoad method:
[_scrollView addConstraint:[NSLayoutConstraint constraintWithItem:_innerView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:_scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];

Take a look at this video. Also, you can try setting the content size of the scrollview problematically as well.
// For verical scrolling
scrollView.contentSize = CGSizeMake(viewToScroll.width, viewToScroll.height * numberOfPages);
// For horizontal scrolling
scrollView.contentSize = CGSizeMake(viewToScroll.width * numberOfPages, viewToScroll.height);

Related

Align Leading Edges of View with constraints programmatically

I am having an UIImageView (say imageView1) and a UITextView(say textView1) which have to be displayed vertically (one [imageView1] below the other [textView1]) beginning with the same margin position as of textView1. I have to achieve this through autolayout programmatically.
I know that this can be done by setting the vertical constraints like below for both the views.
NSLayoutConstraint constraintsWithVisualFormat:#"V:|[textView1]"
But the problem I have here is I already have many text views(textView2, textView3) arranged in horizontal before and after this textView1.
I have already added many autolayout constraints to this textView1 through storyboard. Based on the different screen size and orientation the textView1 margin differs as per the constraints that are provided on the storyboard for this.
Now how can I provide the autolayout constraint programmatically in such a way that my imageView1 is to align in par vertically with the same margin as that of textView1?
p.s: imageView1 is created programmatically in code but where as all other views that I mentioned above are created through storyboard.
+ Adding images for easy understanding
In the image, imageView1 is the UI Image. I have created it in storyboard just for understanding purpose but in real it will be created programmatically and this have to be aligned to the margin of UITextView (textView1) present below it.
This is the constraint that I want to create it through programmatically(In case this is the real question here :).
This constraint is to always make sure that imageView1 and textView1 start originating from the same margin.
How to define this constraint programmatically ?
Rather than using the visual format, you can just instantiate a constraint directly, e.g.
[NSLayoutConstraint
constraintWithItem:imageView1 attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:textView1 attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:0.0]
Use Masonry for setting constraints programmatically.It is very easy to use and reduce lots of complexities for the user.
https://github.com/SnapKit/Masonry
...you can try to build an UIView, set the constraints that it will need, and use it as a placeholder for your UIImageViews.(later you can add them inside of such a view) or, by the otherside, using an UICollectionView instead.
You can set the options argument in
constraintsWithVisualFormat:options:metrics:views: check Apple Class Reference.
Your code might be as follows
NSString* leadingConstraintsExpression = #"V:[imageView1][textView1]";
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(imageView1,textView1);
NSArray* leadingConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:leadingConstraintsExpression
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDictionary];
[self.view addConstraints:leadingConstraints];

Where should I manually add subviews when using Autolayout

I'm trying to force myself to start using AutoLayout everywhere. I have a scenario that is extremely simple using frames but I can't make it work using Autolayout.
I have a StoryBoard I can't touch because it belongs to a library. When the ViewController related to the storyboard loads I want to programmatically add a View. So I created a UIView in the viewDidLoad, added the constraints for height and width and also two other constraints to position them. Everything worked fine BUT the width, I want to have the same width as an other view. How should I do this?
I logged the size of the "other view" in the viewDidLoad and the size is not right, I also logged the self.view.frame.size.width, and is not correct either.
I read that the correct sizes are not set until viewDidLayoutSubviews, so I tried to add my code there but I get in a loop of calls to viewDidLayoutSubviews that never ends. What am I doing wrong?
Try putting the code in viewDidAppear. Check if the subview is already a descendent of the view and if no, then add the subview with the constraints.
If you want two views to have the same width, you should do that with
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:. You can do this in viewDidLoad.
NSLayoutConstraint *widthCon = [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeWidth relatedBy: NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
This assumes that the view that comes from the library storyboard was made using constraints.
You can surely use this method of UIViewController's,
- (void)updateViewConstraints
{
[super updateViewConstraints] ;
// now update your subviews contraints or log your view sizes
}
As you are using constraint based layout and wants to update the subviews constraints depending to UIViewController's constraints then you should always use the following method.

Autolayout & Alignment Constraints

I want to make similar ui for all iPhones & i am currently using auto layout for that, but i do not know how to create this `UI` using `NSLayoutConstraint`?
This UI works fine in small screen but i want same ui for bigger iPhone as well(keep certain amount of space between buttons). how can i add constraint in this to get same ui as iPhone 4s.
i took UIButton & below titles are UILabel.
Some one help me out here.
Screenshots.
Personally I find that trying to make sense of Auto Layout constraints in code (and using NSLayoutConstraint) to be quite confusing and difficult to understand. VFL (Visual Format Language) is powerful but I would recommend if you are just starting out with Auto Layout then working in Storyboard or a XIB file may be easier (depends on your individual preference).
Since this is quite an involved topic I've put together a sample project for you to reference on GitHub. The benefit of this approach is that there is literally no code - all of the configuration is done in the Storyboard (or can also be done in a XIB file). Highlights of the constraints I created are below however please reference the sample project for specific details:
Background View (Purple) - Constraints pin the top, bottom, left and right sides to the edge of the parent view.
Icon Container View (White) - Constraints pin the bottom, left and right sides; a height is also set which accommodates all of the icons.
All image views have a width constraint and aspect ratio constraint (maintains equal width and height) and all labels are constrained to their appropriate image with the appropriate vertical constraint (top).
The outside corner icons are all constrained only to their corner (top-left, top-right, bottom-left, bottom-right). The center corners are constrained to be centered horizontally inside the view.
If you are just getting started with Auto Layout you may find the following presentation I made helpful in learning the basics.
Here are three screenshots from different simulators which show how the layout adjusts automatically depending on the screen size: iPhone 5s, iPhone 6, and iPhone 6 Plus:
Technically, landscape orientation is also supported by these constraints however the result may or may not fit your requirements. (I am unsure what orientations you are planning to support.)
I don't really want to write the code for your whole view as its not the best way for you to learn and plus I don't have the time right now. Instead, I will tell you the approach I usually take:
Create the view
UIView *myView = [UIView new];
Set translatesAutoresizingMaskIntoConstraints property
myView.translatesAutoresizingMaskIntoConstraints = NO;
Add view as subview
[self.view addSubview:myView];
Add the constraints
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:myView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0];
[self.view addConstraint:constraint];
You need to add multiple constraints in order to layout the view exactly as you want in relation to the other views around it. Top, left (leading), right (trailing) and bottom.
I suggest either creating your own category in order to make writing the constraints quicker and easier to understand when reading it back. Here's one for example: PureLayout
You can also use constraints with a visual format but I often find this can be harder to get your head around.
More reading on the whole subject can be done here
It depends on your requirement and their may be different ways to achieve. But if you ask in this particular case with three items as fixed then following may help:
The buttons at the corners can have constraints with borders.
All the labels can have constraints with their respective buttons to be same width and should be aligned horizontally centred with buttons.
The buttons in centre and buttons at the borders should not have any dependencies with each other.
The buttons in centre can have constraints with top and bottom of the container view.
Buttons in centre should have constraints for horizontally centred with the container view.
You might need to add some more constraints, but based on the above suggestions it will work for any screen size.

Evenly Space UIViews of equal sizes in superview using Auto Layout in iOS 6

I am in a need of having the series of buttons to be evenly placed in superview Horizontally using Auto Layout.
Here, I want to keep the sizes of the subviews same, only the center of the subviews will be placed in such a way that there is equal number of space between them.
Note: I dont want to set the Size of the superview, I want every thing to be Auto Layout-ed.
Please Help,
I am stuck !!
Thanks!!
You can create as many UIView's as you have buttons, and center the buttons inside the views, the views can be aligned back to back, using this code:
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view1][view2][view3]|"
options:0
metrics:nil
views:views]];
Make sure you first remove existing constraints from the superview using:
[self.view removeConstraints:self.view.constraints];
and in the viewDidLoad turn off auto resizing conversion:
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];

Setting constraints programmatically

I'm experimenting with how to use UIScrollView. After much trouble, I finally got the hang of it. But now I've seem to hit another snag.
In this simple app, I have a scroll view with and in order for it to work, I have to set the view's bottom space to scrollview constraint to 0 as described here and it works fine. I'm doing it through the IB.
Now I've come across a scenario where I have to do that part programmatically. I've added the below code in the viewDidLoad method.
NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[self.view addConstraint:bottomSpaceConstraint];
But it doesn't seem to work. It outputs the following message in the console window adn i don't know what to make of it.
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)
(
"",
""
)
Can someone please tell me how to do this? I've also attached a demo project here so that you can get a better idea on the issue.
UPDATE:
First off thanks for the responses. Using the ways mentioned in the answers I was able to get it working. However ina slightly different scenario its not. Now I'm trying to load a view onto a viewcontroller programatically.
If I may explain further. There are 2 view controllers. First one being a UITableViewController and the second one a UIViewController. Inside that a UIScrollView. Also There are multiple UIViews and some of those views' height exceeds the normal height of the screen.
The UITableViewController displays a list of options. Based on the user's selection, a particular UIView out of the lot will be loaded into the UIViewController with the UIScrollView.
In this scenario, the above method doesn't work. The scrolling isn't happening. Do I have to do something different since I'm loading the view separately?
I've uploaded a demo project here so that you can see it in action. Please see the Email.xib and select Email from the table view list.
Based upon a review of your code, a few comments:
You generally don't need to adjust constraints when a view appears. If you find yourself doing this, it often means that you haven't configured your storyboard correctly (or at least, not efficiently). The only time you really need to set/create constraints is either (a) you're adding views programmatically (which I'm suggesting is more work than it's worth); or (b) you need to do some runtime adjustment of constraints (see third bullet under point 3, below).
I don't mean to belabor it, but your project had a bunch of redundant code. E.g.
You were setting the frame for the scroll view, but that is governed by constraints, so that does nothing (i.e. when the constraints are applied, any manually set frame settings will be replaced). In general, in auto layout, don't try changing frame directly: Edit the constraints. But, no changing of constraints is needed at all anyway, so the point is moot.
You were setting the content size for the scroll view, but in auto layout, that, too, is governed by constraints (of the subviews), so that was unnecessary.
You were setting constraints for the scrollview (which were already zero), but then you weren't adding the view from the NIB into the scrollview, defeating any intent there, too. The original question was how to change the bottom constraint of the scroll view. But the bottom constraint for that is already zero, so I see no reason to set it to zero again.
I'd suggest a more radical simplification of your project:
You're making life much harder on yourself by storing your views in NIBs. It's much easier if you stay within the the storyboard world. We can help you do the NIB stuff if you really need to, but why make life so hard on yourself?
Use cell prototypes to facilitate the design of the cells in your table. You can also define the segues to go from the cells to the next scene. This eliminates any need to write any didSelectRowAtIndexPath or prepareForSegue code. Clearly, if you have something you need to pass to the next scene, by all means use prepareForSegue, but nothing you've presented thus far requires that, so I've commented it out in my examples.
Assuming you were looking for a practical example of programmatically changing constraints, I've set up the scene so that the text view will change its height programmatically, based upon the text in the text view. As always, rather than iterating through the constraints to find the one in question, when altering an existing constraint that IB created for me, I think it's far more efficient to set up an IBOutlet for the constraint, and edit the constant property for the constraint directly, so that's what I've done. So I set up the view controller to be the delegate of the text view, and wrote a textViewDidChange that updated the text view's height constraint:
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
self.textViewHeightConstraint.constant = textView.contentSize.height;
[self.scrollView layoutIfNeeded];
}
Note, my text view has two height constraints, a mandatory minimum height constraint, and a medium priority constraint that I change above based upon the amount of text. The main point is that it illustrates a practical example of changing constraints programmatically. You shouldn't have to muck around with the scrollview's bottom constraint at all, but this is shows a real-world example of when you might want to adjust a constraint.
When you add a scrollview in IB, it will automatically get all the constraints you need. You probably don't want to be adding a constraint programmatically (at least not without removing the existing bottom constraint).
Two approaches might be simpler:
Create an IBOutlet for your existing bottom constraint, say scrollViewBottomConstraint. Then you can just do
self.scrollViewBottomConstraint.constant = 0.0;
Or create your view initially in IB where the bottom constraint is 0.0 and then you don't have to do anything programmatically at all. If you want to layout a long scrollview and it's subviews, select the controller, set it's simulated metrics from "inferred" to "free form". Then you can change the size of the view, set the scrollview's top and bottom constraints to be zero, layout everything you want inside the scroll view, and then when the view is presented at runtime, the view will be resized appropriately, and because you've defined the scrollview's top and bottom constraints to be 0.0, it will be resized properly. It looks a bit odd in IB, but it works like a charm when the app runs.
If you're determined to add a new constraint, you could either programmatically remove the old bottom constraint, or set the old bottom constraints' priority down as low as possible, and that way your new constraint (with higher priority) will take precedence, and the old, low-priority bottom constraint will gracefully not be applied.
But you definitely don't want to just add a new constraint.
It's possible to create outlets to represent layout constraints in your view controller. Just select the constraint you want in interface builder (e.g. via "select and edit" on the measurements pane of the view you are arranging). Then go to the outlets pane and drag a "New Referencing Outlet" to your code file (.h or .m). This will bind the constraint to an NSLayoutConstraint instance that you can access from your controller and adjust dynamically on the fly (generally via the constant property, which is poorly named because it's not a constant at all).
(Note that in XCode 6 you can double-click the constraint to select it for editing.)
Be careful when adjusting the layout in interface builder, however, as you may end up deleting the constraint and have to re-bind it to the outlet.
Looking at the console information, i feel that you are creating ambiguity when you add two same type of constraint.
So instead of creating and adding new constraint, try updating the previous constraint that is already in the constraints array.
for(NSLayoutConstraint *constraint in self.view.constraints)
{
if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom &&
constraint.firstItem == self.view && constraint.secondItem == self.scrollView)
{
constraint.constant = 0.0;
}
}
Hope this helps
Even Rob's answer will work!
You can use https://github.com/SnapKit/Masonry for adding constraints programmatically.
It is power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout.
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:#[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],]];
in just few lines
Heres the same constraints created using MASConstraintMaker
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
Or even shorter
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
do your best is sort ;)

Resources