UICollectionView lets you define custom views for cell items, for supplementary views, and for decorative views. These must all subclass UICollectionReusableView, so that the collection view can dispose of them to save resources when they are offscreen.
However, I want to add a view which scrolls along with the rest of the collection view's content, but which will never be disposed of. That is because this view contains some complex state I do not wish to manage in the view controller (long story). This view is a bit like a decoration view, in that it does not depend on data vended by the UICollectionViewDatasource.
So what is a valid way to do this?
Option 1. Is there a way to make this a decoration view, but somehow mark it so that the collection view will never dispose of it when it's offscreen?
Option 2. If not, is the best alternative to just add this as an ordinary subview to the collection view, taking advantage of the collection view's functionality as a scroll view? Is this supported by the collection view explicitly, or am I at risk that manually adding a subview will break the collection view's own layout management?
Option 3. If this isn't supported by collection views, then is there another conventional best practice for this case? For instance, I could add the decoration-like view as a sibling to the collection view, and then try to hook into the collection view's pan gesture recognizer, but this feels hacky and fragile.
Since
this view contains some complex state I do not wish to manage in the view controller
This is the root of the problem. It's never a good idea to use views to store state. You say that you're looking for a "best practice," but the truth of the matter is that the best practice is to move your state management code out of the view and into a model object.
The separation between model and view is is there pretty much for the reason you've discovered -- views have different lifetimes, relationships, and responsibilities than model classes. Any solution to your problem that tries to turn a view into a model class is likely to give you that fragile, hacky feeling.
It seems like my question is a duplicate (at least in the likely special case of a header or footer view) of How to add HeaderView in UICollectionView like UITableView's tableHeaderView .
And that answers shows that one solution is to use the contentInset property on the UICollectionView to create space for headers or footers, and then manually add them as subviews into that space.
Another solution that worked for me is to subclass UICollectionViewFlowLayout to create an additional space, instead of using contentInset.
However, using the contentInset is obviously more straightforward. The only difference I can see is that the inset-based method means that the collection view's contentSize only reflects the cells it manages, and not the manually added header and footer views. I don't see any problems with this.
Related
Please see my mockup pictured above. I am a bit puzzled as how to code this. I guess I would use one UITableView and have all customizations on one cell. Any other ideas? I wasn't sure if it would be better to have multiple tableviews on one controller or something instead.
First off, this design really won't work except on iPad. It's just too much to try to cram onto an iPhone screen.
Collection views are like a more flexible form of table view. You can arrange collection views in rows, columns, rows AND columns, circles, or whatever you want.
You can make a collection view act like a table view, but it's more work, and a little more confusing.
If your UI is a vertical list of cells, a table view might still be a better fit, since it matches what you're doing.
Assuming this app is iPad only:
Create a custom subclass of UITableViewCell, with it's own XIB file. Define the contents as desired. Making each "tile" (the 5 boxes shown in each cell) in the table view cell a separate custom subclass of UIView might make sense, or it might not, depending on how you expect to use them. If they are always in the same order, and never used anywhere else but in this table view, then no. Just build the cell from components. If you ever think you might use one of these tiles somewhere else, or if you might ever display them in a different order, then yes, make each tile a separate custom UIView subclass, and use those custom views in constructing your cell.
Having multiple UITableView is bad idea – when you scroll one of them others will remain static. I would recommend you to use one table view but decouple a cell in the separate views (or even view controllers) and have a special view (maybe with .xib) for each of 5 components.
From an expense point of view, which option provides the best computational results when adding subviews to a UIView's hierarchy:
Scenario 1
In viewWillAppear:animated or willLayoutSubviews remove all subviews of the container view using [[aView subviews] makeObjectPerformSelector:#selector(removeFromSuperView], reset all pointers or properties and then (re)initialise all subviews and add them to the container view hierarchy.
Scenario 2
Initialize and add all subviews in the viewDidLoad method (called once only, thus only adding them to the view hierarchy once, then in the viewWillAppear:animated or willLayoutSubviews methods setting the constraints or frames for each subview.
Scenario 2 is much more faster than the first one. Creating and deleting views is expensive you should try to limit as much as possible.
If you are using autolayout setting the constraints can also be done in the viewDidLoad, and setting the frame is not recommended.
So the best is to create and add all the subviews in the viewDidLoad set up the autolayout constraints there and do not set the views frame, only the constraints in other methods if needed.
Similar to your condition there is another condition :
Take the situation where you need to present a view when the user taps a button. There are at least two approaches to this:
1.Create the view when the screen is first loaded and hide it; then when you need it, show it.
2.Do nothing until you need to show the view. Then, create the view and show it, all at once.
Each approach has its own pros and cons.
Using the first method, you consume more memory because you
immediately create the view which holds on to that memory until it’s
released. However, when the user does taps the button, your app will
appear more responsive as it only needs to change the view’s
visibility.
Taking the second approach will have the opposite effect; by creating
the view only when it’s required, you consume less memory; however,
the app won’t appear as responsive when the button is tapped.
I have to implement a view controller (on iPhone, portrait only, full screen view) where the upper part of the view must have an horinzontal, paged scrolling behavior, potentially infinite.
I already used for similar purposes UIPageViewControllers, to take advantage of the datasource and delegate protocols, which are very helpul for manage memory and other stuff (keeping only 3 view controllers in memory, providing delegates to handle actions exactly when a transition is done and so on): so I think that in this case too this component is the best choice.
But here comes my problem. In the view I'm realizing, I have to let the user understand that he can swipe left and right to move to another view: a page control is not a good choice, since the scroll could be potentially infinite, so I would like to let a small portion of the views of the left and right view controllers to be visible.
Something like that:
link to the image (sorry I cannot include images in my posts yet)
Up to now I have not been able to figure out how to realize this. In the options during initialization, UIPageViewControllerOptionSpineLocationKey can be specified to set (from documentation) "Space between pages, in points": but this seems to work only with positive value, so that the space increases, while it ignores negative values that could reduce the space.
I hope there might be a solution using page view controllers, since at the same time I need to refresh a table view in the lower part of the screen when a transition is complete, and the delegate method of page controllers is ideal for this aim.
Maybe a collection view can be an alternate solution, but it is more complicated and I'm not sure how to obtain a behavior like the one I described to refresh the table view.
If needed I can attach some code and a screenshot of the prototype
Ok, I understand that this is not possible and why it is.
The datasource methods load, when needed, the view controllers that are before and after the current one. Making these view controllers' views always visible, as I desired, will require that the datasource loads more than one view controllers after (or before, depends on the direction of scrolling) the current one, to be ready for the pan actions (potentially, before the animation is ended by the user lifting up its finger, two view controllers "after" or "before" could become visible it my desired configuration), and this is not intended by UIPageViewController on iPhone, especially in portrait mode.
So actually, the only way to achieve that more than one view is visible in an horizontal-scrolling component at any time, is to implement a UIScrollview with horizontal paging and calculate the contentSize and other sizes accordingly.
I went through lot of similar questions but still have not clearly understood this.
In terms of better design - what is right way - creating all the UIButtons, UILabels etc in the view controller itself and then add them as subviews, or should I create a custom view (#interface MyView : UIView) with all the required buttons/labels etc and then assign that view to the view property of View controller? I am not using interface builder.
Is there any real need/advantage of creating a custom view like this or adding everything in view controller itself should be okay/good idea? Sorry I am very new to iOS app development :-)
If someone could explain it to me - would be really helpful.
Some advantages of subclassing a view are:
Code separation. If you have a complex view and want to keep your viewcontroller clean, subclass and separate it out.
Reuse. If you reuse the view anywhere else it is possible with minimal effort when the view is it's own class.
You can choose what setup and methods to expose for setup
Some disadvantages of subclassing a view are:
View no longer will have access to view controller ivars.
View controller will no longer have direct manipulation on the view
You are constrained to what the view publicly exposes in terms of setup and config (may be a good thing).
Overall, there is no single best answer, it all depends on your setup and how you like to keep your project organized.
My non-answer : the answer lies in between these 2 opposite sides :
putting verything in the view Controller, using just basic views
using very elaborate views that abstract away some tedious graphic work.
To get an idea of what I'm talking about : one might implement a UITableView behaviour directly into a UIViewController with just a UIScrollView, and handle all the indexPath computation (depending on the amount of scrolling), views recycling, etc... in this very viewController.
But, as lists are a common way to visually display information, all this 'recycling view', 'setting content for currently displayed cells' has been moved into a custom view class : UITableView, and delegate and datasource patterns have been use to make this class behavior easily customizable.
Creating abstractions has its advantages and disadvantages.
I try to do things incrementally :
hold the most you can in viewController
when code becomes too complex, or if you see yourself needind the same kind of 'component' elsewhere : the create a custom view, and try to define its API (what properties it exposes, what implementation it hides)
View controllers are a nice way of separating the things that a view must do in the strict sense (handle input, render output) from the larger hierarchy, manipulation, and information routing of views around it, which the view controller coordinates.
Striking the right balance depends on a number of localized factors. A view which contains a label and an image (like a cell) could certainly "own" its subviews and appear to the controller like one container unit. But note in this case that the subviews are also supporting the input/rendering of the "cell" unit.
In that sense, if you have a large canvas with a set of actual controls that are "on top" of that canvas but not necessarily "owned" by it, the view controller would probably want to create and attach these into the view hierarchy.
Often, when I'm making my apps, I'm in this situation : I have a UINavigationController, handling the view stack, some UIViewControllers, controlling their respective views...
But when I want to add several custom UIViews in my mainView, I don't know how to manage my code.
Each UIViewController needs to handle one and only one view (wich normally occupy all the screen size), and a view should not control their content (update it a the extrême limit).
You can't neither do this :
[myViewController1.view addSubview:childViewController.view];
So if I want to achieve something like this, what should I do ?
The orange parts have to be 3 instances of the same UIView(Controller?), but with a content depending of a NSObject (User, obviously).
I think this very important to segment your content, this should be an easy problem, but I found a lot of contradictory answers so, what's the best practice to handle this common issue?
Theses orange views should be instances of UIViewControllers in order for it to handle their UITableViewDatasource? Is addChildViewController relevant in this case?
I already found a lot of things which work, but I don't know what should I do...
Also, I'm using xibs.
Thanks in advance if you can help me (and other people I think).
You can do it either way (view or view controller) depending on how you want to handle things. Certainly, you can have one object be the data source for multiple tables, so in that case, you would just add multiple views. If, however, you want to keep your code more compartmentalized, then add view controllers, and have each control its own view -- to do this, you do need to use addChildViewController, and use the methods that Apple describes for creating custom container controllers. Alternatively, you can use container views in a storyboard which makes the process of creating custom container controllers simpler.
You're on the right path... Create separate instances of your subviews, and add them to your view. If you will have more than 3 (for instance, imagine coverview for your music, and you could scroll indefinitely left and right), I'd take a look at UICollectionViewController ... That will help manage cell re-use.
But, if it's just 3, just create three instances with different frames and add them to your view.
Here's how I'd do it:
each orange box will be a custom view (inherits from UIView)
the view will have the label, image and the tableview.
since you are not sure of the number of instances of these views you'd be using, its better to use some kind of tagging, so that you can have one place for the datasource and delegate methods of the tables in these orange views.
in the datasource and the delegate methods, you can make use of the tableView.tag (same as the orangeView.tag property).
I personally dislike having more than one viewController in a view (except the splitVC), probably because I haven't had a such requirement.
I dont see how a uiviewcontroller for orange box would help, over a uiview.
as #James Boutcher mentioned in his answer, UICollectionViews will simplify this issue further.
Why not creating a UIView class and overriding the drawRect method and then adding subView for this class in your myViewController1.view