I've always done my UIs in code but have decided that I should use storyboards and auto layout for a current project. Everything had been going well until I built a complex scene with about 50 views with lots of hierarchy and some grid of views.
The problem is that my auto layout is getting muddled on some devices and orientations. I'm finding it challenging to use IB to try fixing the dozens (hundreds?) of constraints or to track down the problems and resolve them. The situation is such that I'm not getting errors or warnings, just some unpleasant layouts at times. And IB can be a pain with all the clicking and changing settings you need to do to track down constraint information, let alone get a full idea of how they all relate in a scene.
I've just spent a day reading docs and background material on auto layout and constraints and it seems my best solution is to use the visual format to specify constraints in code and create some custom code to help. However, I can't seem to find anything on how to make the transition from IB to code.
Specifically, should I wipe all IB constraints and do them all by hand or is it possible to be selective? I ask because I have some groups of views in containing views where the content views have a perfect layout.
Secondly, where best do I put my code? I want to coexist storyboards and just want to selectively modify some complex scenes. Is a view controller's viewWillAppear: the right place to modify or remove/add constraints for the view it controls?
Connect an IBOutlet for the NSLayoutConstraint you want to be able to modify in the storyboard/xib file to your controller/view class.
Once you have the layout object connected, you can modify the .constant property and animate the view:
[self.containerView layoutIfNeeded]; //make sure all layout operations are done
self.containerViewBottomLayoutConstraint.constant = 200.0; //change the layout
[UIView animateWithDuration:duration animations:^{
[self.containerView layoutIfNeeded]; //animate the changes
}];
updated: you can add your modification code in viewDidLoad, awakeFromNib, viewDidAppear, or event based. It really depends on your intentions.
Sorry to take so so long to get back to this while other projects intruded.
I had to do a lot of refactoring to simplify my scenes so that auto layout could do the right thing, and yet I am not fully satisfied with the results. The problem seems to be that IB is just not easy to use with lots of items, and that auto layout is complicated, by necessity.
With that said, the best results I've seen so far are drawn from this article by Justin Driscoll: http://themainthread.com/blog/2014/02/building-a-universal-app.html
He advocates building custom views to encapsulate reusable UI components. I have taken this approach but have extended the idea to also bundle up related components that are not going to layout very differently as the layout changes. For example, I have a progress bar with button and two labels, so even though I am not reusing them as a group, they need to be adjacent and conceptually are related, so I've made a custom view for them which handles the auto layout as Justin suggests.
I'm now taking the approach that each level of auto layout should only have a handful of elements. If one level gets too complex, I'll bundle up some related items in a custom view and push some auto layout inside that new view. So far it isn't too bad.
Auto layout can be really tricky when using that many views. I have used similarly complex views structures, and I find that it is best to try to keep all of the constraints in code or in IB. Right now we are keeping them in IB. The only time that we move a constraint into code is when we are supporting a different screen size, and we need to modify a single constraint for the view to work right. I have always modified the constraints in viewDidLoad myself.
When something gets messed up I almost always have to nuke all of the constraints on that view and start over. It sucks, but it's often quicker than tracking down the problem. One thing that we do that makes it easier to deal with that sort of thing is to use .xibs along with your storyboard. That way, each view can handle it's own layout, and you can pull that into a view that is sitting in a storyboard.
Related
I'm having a hard time wrapping my head around how frame properties (height, width, posX, posY) work in terms of setting them in a View Controller vs on the Storyboard (Interface Builder).
For instance, let's say I have a UICollectionView object that I set to have a width of 400 and a height of 800. Then, in my code, I set the frame of that same object to 600 x 400. I haven't really found a consistent behavior. I tried setting the frame in viewDidLayoutSubviews and it sort of worked - but it seem to 'jump' back and forth between that and what was set on the storyboard.
Basically my question is, when do the properties on the storyboard change the UI object? I assume that I just need to know that, and then reset them in the View Controller after the fact. Or, is there a way to set the height and width empty so that I can do it all in the code?
Any insight into this would be very helpful!
If you use AutoLayout in your project then setting frames of the view objects you configured in storyboard won't work. Because after you set the frames, AutoLayout will update frames again which makes the frames set by you not working. If you want detail, you can check this article:Advanced Auto Layout Tools But you can set frames of view objects created programmatically to position them.
You can check if you have turned on AutoLayout in you storyboard file's file inspector. There is one thing though, if you do want to use AutoLayout, be sure to not set view's translatesAutoresizingMaskIntoConstraints to NO. The default value of this property is YES. If you use AutoLayout, this property is set by storyboard for you, because constraints created in storyboard are enough to layout the views.
EDIT:
1) if you are not using AutoLayout then setting frames in code should work as expected.
2) yes you can, a little tricky though. you must create UICollectionView yourself using [[UICollectionView alloc] init] or load it from nib. and then configure cell in IB with a xib file. you can use AutoLayout to layout subviews of cell in xib file. and register the class of cell to UICollectionView or load cell object from nib yourself. then you should calculate the size of every cell and let AutoLayout layout subviews of cells.
although this is easier than layout interface entirely in code, it's still a little complicated. the better way is using AutoLayout. Since not all the layout detail can be done in the design time since some views' frame may be different depending on data. you can make a basic layout with AutoLayout first, then IBOutlet the constraints you want to configure on the fly. and change the constant property of constraint objects later. this way, you can 100% control the layout process and also let AutoLayout do the dirty jobs you don't want to do yourself. I suggest you read official docs of AutoLayout and other good resources about it. The learning curve is steep at first, it may make you want to kill yourself too. But it's really powerful and easy to use. once you figured out how AutoLayout works, it will make your iOS development life much easier.
If you want to set the size of things through code, you could try creating outlets from the storyboard to the View Controller. Then in the View Controller, you can use viewDidLoad or viewDidAppear to set the size properties of your object(s).
viewDidLoad will get called when that view is first created. viewDidAppear will get called each time that view comes back onto the screen (like if you are going back with a navigation controller).
So far in my iOS project, I've done almost everything in code. I've laid out my views by setting frames manually in the layoutSubviews and viewWillLayoutSubviews methods. However, this is a very manual way and I'm considering other methods, such as AutoLayout...my problem is that I'm inexperienced with AutoLayout (and other methods such as using Auto-resizing masks).
My problem with manually laying out is as follows:
Let's say I'm adding subviews to a view controller's view. Now let's say I'm adding subviews to those subviews, etc. My problem is that many of those child views are dynamically-sized, i.e. their size depends on data from a server.
So now it comes time to layout the child views' frames from the parent view - since parent views define the frame of their child views before the child views layout. But while the child views have been instantiated, they haven't been laid out, so they haven't set their frames, so I don't know how large they will be.
This has been extremely annoying. My solution to this problem has been to implement a method for these child views which returns how large they will be based on their data. This is extraneous, time consuming and tedious.
My question is - will switching to AutoLayout (or Auto-resizing masks) make these problems go away? Will they lend themselves to the same amount of re-usability that setting frames manually does? Am I just doing something wrong in general?
If anyone have Google+ App can certainly understand what I'm trying to implement.
(explained here: UIViewController Containment with animation like Google+)
I think it has something related with the new effect in iOS 7 Calendar App.(explained here: Recreating iOS 7 Calendar UIView Animation)
-
This is a common animation effect that I'm seeing in many apps these days.
Months ago, the fellow Rob tried to help me with this his answer:
Now I was trying to implement it but there's a problem. Images explains better:
INITIAL STATE
WHAT HAPPEN WITH CURRENT IMPLEMENTATION
WHAT SHOULD HAPPEN
I've created a super simple project that shows the implementation (few lines).
Can someone help me to find where's the problem?
REPO: https://github.com/socksz/MovingTableViewCellContent
The problem is that you're trying to change the view's frame with Auto Layout on. You can't do that. The Auto Layout system will overwrite your changes. Try turning off Auto Layout in your storyboard and you'll see that it works.
So your options are:
Don't use Auto Layout
Use/manipulate constraints instead of frames.
For (2) you can just go into the storyboard and set up width and height constraints on the container view and it will work. If fixed size isn't the exact behavior you want, you'll need to be more explicit in your requirements.
The default constraints you're getting now are attached to the parent view and aren't getting carried along for the ride when you move the view to a new parent.
I find myself often having to reposition subviews of a view after hiding or showing one of them. The way I'm doing this is by programmatically changing a view frame's origin and/or size, or its center. But is there an easier way I'm missing? Is there a way to do it with Autosizing masks?
I don't think there's any automatic way of doing this. You could probably get clever about how you do it programmatically (e.g. if you used a set of sequential tag identifiers, you could loop through and calculate the height of the previously visible tag to calculate the origin of the next subview; or if there are a group of subviews that are always going to move together, you could put them in a container UIView and thus move a whole bunch of them by just moving their container view; etc. ... it depends upon how they're laid out and which fields might be hidden).
This won't help you for now, but check out WWDC 2012 session 202 for a discussion of a relevant improvement in iOS 6.
Within the pageViewController:viewControllerAfterViewController: method, just before the return statement, the view which is about to be returned as the next page has the correct view frame size.
However immediately after the pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted: method is called, I check the frame size of the newly introduced view controller ([pageViewController2.viewControllers objectAtIndex:0];) and I find it resized.
Note that, I have set [self.pageViewController.view setAutoresizesSubviews:NO] and the autoresizing mask to None for the newly created ViewController.
Any ideas in which step the new ViewController is being resized?
I think the problem is inherently related to the nature of UIPageViewController. It is built from UIScrollView. I don't exactly know why there is strange resizing behavior, but it seems to be particularly pronounced when the view controllers that make up your pages use auto layout. Seemingly, locking the constraints in your page view controllers to the superview makes the elements resize after the transition because the superview is itself getting resized after said transition.
This sucks because Apple is basically pushing all of us to adopt auto layout. Auto layout is awesome, and I recommend everyone use it from now on, but it really really sucks when you use it with a UIPageViewController. They really ought to either scrap that class or build something easier for developers, something that can be dragged into a storyboard outright.
A few things to consider.
1.) Don't lock anything to the "Top Layout Guide" or the "Bottom Layout Guide". Also make sure you have "Constrain To Margins" disabled on any view intended to hug the sides of the screen.
2.) If you are using a label in your individual page / content view controllers, make sure you bind/constrain it to something other than the superview. I wanted to place a label over a UIImageView, so I aligned the label to the leading and top edges of the image view (using AutoLayout constraints only), creating an offset to give the label some margins.
3.) The following would otherwise be a good tutorial. However, it is a bit outdated. I downloaded the project and basically modified it to get a UIPageViewController implementation that works. The only problem with this project is that it doesn't use AutoLayout. I'm currently writing a blog post that more clearly discusses how to use UIPageViewController and Autolayout together.
http://www.appcoda.com/uipageviewcontroller-storyboard-tutorial/