I find myself wanting support for delegating the responsibility of layoutSubviews for a UIView. This to avoid having to make a specific subclass just to implement the layoutSubviews method. I'm suspecting the reason I found my self wanting this alternative might be because I've missed some fundamental UIKit design considerations, or? Or should I just go ahead and make my own UIView subclass with support for this type of layout delegation?
You're going to want to create a subclass whenever you need a custom layout, there is no way to delegate. It's also the only sane way to do it- if you're containing a set of views in a superview, that view should control its subview layout for maintainability.
You also don't have to do view layout in layoutSubviews- you can simply create the layout when you create the superview, and assign positions and sizes at that point, if you don't need something reusable.
Related
This may sound like a silly question, but most of my experience with iOS development has revolved around Auto Layout being prominent, and I'm curious now (for performance purposes) how I'd go about laying out a cell without Auto Layout and using only frames (and perhaps auto-resizing masks). Turns out this is very hard to Google due to the prominence of Auto Layout.
Essentially am I setting up and adding the subviews in the initWithStyle method of the UITableViewCell subclass?
Positioning wise, am I just relying on the bounds of the contentView, and then if I want one view beside another view, would I basically do newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing?
What happens when I rotate? I know I can watch for rotation in viewWillTransitionToSize, but how do I go about re-positioning the cells? Simply calling tableView.reloadData() would be both expensive and not do much as the cells are laid out in initWithStyle, correct?
I'm targeting iOS 8+.
Any insight would be truly appreciated.
Actually its
newView.frame.origin.x = CGRectGetMaxX(otherView.frame) + spacing + leftMargin;
// you have to include all the spacing, including margins
Tip:
Do not rely on bounds of the contentView by default it is set to maximum width of 320, i suggest you use main screen's frame for that.
AutoLayout is our friend and can save us a lot of time.
how do I go about re-positioning the cells?
If you're planning to do it programmatically, you need to setup the new height and width of the view after the rotation.
Calling tableView.reloadData() to update the views in the cell is not expensive, that's how it works. We don't have a choice but to live with it.
Then it will be better to write the frame calculation portion in viewDidLayoutSubViews.
I'm not entirely certain I understand your question. Please let me know if this makes sense:
Adding Subviews
To add subviews programmatically, it's actually best to do so in the override of layoutSubviews by checking if the contentView has subviews already added and calling a method to add them if needed. This way, you can load the table cell via either a storyboard/nib or using initWithStyle. Alternatively, you can use a shared commonInit method that you call in both initWithCoder and initWithStyle.
Layout of Subviews
To layout your subviews programmatically, you override layoutSubviews (and remember to call super) and set up the frames in that method. This method will be called whenever the view changes size (rotation, initial presentation, etc) and will always include the current bounds of the content view. To calculate your subview frames, you can do so as you've suggested, but you need to define the subview frame and then set the frame for the view:
frameB.origin.x = CGRectGetMaxX(viewA.frame) + spacing;
viewB.frame = frameB;
And keep in mind that this will not correct for the width of frameB. Therefore, you might want to consider using CGRectDivide() instead.
Scrolling performance
That said, the big performance hit when using auto layout in table view cells is not auto layout itself, but the calculations that are done many times on the same set of data to lay it out - all on the main thread. Such as calculating frames for all the text to layout a bunch of labels relative to one another or something that uses drawRect rather than drawing to an image bitmap on a background thread and then loading the image in to a UIImageView once the drawing is complete. Without knowing what data you are displaying it is hard to guess what is causing the performance issues, but you should be sure to use profiling to determine if you are actually improving performance. Additionally, you may need to consider moving some of the number crunching and/or image rendering onto a background thread.
Good luck!
In the question "When does layoutSubviews get called?" several cases are listed:
The parent view is resized
The device is rotated
Any more?
If there is no requirement to handle, for UITableView, we can create a header view for the section header by creating one customized UIView. The simplest way is to create the UIView by initWithFrame: and then there is no need to relayout any more.
Therefore, in my opinion, we do not need override layoutSubviews etc. But there are some opinions to insist overriding it.
Reasons to not use it:
KISS (keep it simple and stupid )
No requirement for relayout
Relayout has cost when scrolling
My question here is to get more suggestions for this case. Thanks.
You don't usually need to override layoutSubviews. There are basically only two reasons:
Your custom UIView subclass has subviews that you want to rearrange using manual layout
Your custom UIView subclass needs to know when its size changes for some other reasons, like shrinking a font size or doing some manual drawing
Generally speaking, you don't need to override this method. Most layout is done either by the view controller setting the view's frame at appropriate times, or by using auto-layout.
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).
I'm trying to get a handle on the best way to apply Auto Layout constraints in code instead of IB.
I need the topLayoutGuide and bottomLayoutGuide property of my ViewController and the developer docs say to query for it in the viewDidLayoutSubviews selector. In the other ViewController lifecycle methods the top and bottom layout guides haven't been set yet.
My question is that since this can be called numerous times it feels bad to do this in viewDidLayoutSubviews multiple times:
Remove prior constraints in the view.
Set up the constraints again.
Call [self.view layoutSubviews] to apply them again.
Is there a better place to do this? For instance where I know a selector will only be called once and I apply my constraints. If any keyboard actions are called I can update them appropriately instead of destroying the others and rebuilding from scratch.
I know the documented advice is to use UICollectionViewFlowLayout if you are doing anything "like a grid or a line-based breaking layout". However, I am not sure this is true for my case.
I want a grid but do not want a line-breaking layout. Items should be laid out infinitely both horizontally and vertically without ever stacking. Essentially, a giant chessboard that scrolls horizontally or vertically if content goes beyond the frame.
To subclass UICollectionViewFlowLayout I would have to:
Override prepareLayout to stop the layout from wrapping items. This seems like a lot of work.
Override collectionViewContentSize.
Apple says they have done "lots of hard work" in crafting UICollectionViewFlowLayout, so I should leverage it if I can. But if I have to override prepareLayout to turn off line-breaking, I suspect that I am throwing away a large part of their work. Of their work that is left, I probably will not use most of it anyway (for example, minimumLineSpacingForSectionAtIndex).
Because the layout I want is so simple, I suspect that I should subclass UICollectionViewLayout instead, because:
I will have a simpler and cleaner implementation with everything in one layout class instead of spread between a subclass and a delegate.
I don't think it will be that much harder than subclassing UICollectionViewFlowLayout because I have to override prepareLayout in both cases, and I suspect that is where all the hard work will be.
I'll be in a better position to tweak other UICollectionViewLayoutAttributes in custom ways than trying to add another kludge on top of a UICollectionViewFlowLayout subclass.
Is my conclusion correct?
UICollectionViewFlowLayout can't support two directions anyway, it scrolls along one axis only, either horizontally or vertically. So you have to subclass UICollectionViewLayout not UICollectionViewFlowLayout.
Then you have to override prepareForLayout, layoutsAttributesForElementsInRect methods as you said correctly..
The layout you describe (items arranged in an indefinitely long horizontal line and sections arranged in an indefinitely long vertical line) resembles the "featured" section of the App Store :)
I have been meaning to use a similar concept in some of my apps too, and I think the trick here is that it's not handled by a single UICollectionView. It appears that what you are looking for can be achieved by using a UITableView as a base, and have each section of your content take up a single cell in the table. That is, each UITableViewCell would contain a UICollectionView with horizontal scrolling.
The key limitation of UICollectionView that is sometimes not trivial to understand is that it is, after all, a single scrollView. You can override some functionality to enable scrolling in both directions, but if you want some content to scroll one way, and some content to scroll another way, you would have to create nested scrollViews.