App structure: UIView vs. UIViewController in a paging interface - ios

There are some great answers relating to when to use a UIViewContoller vs. a UIView. For example here and here.
The general gist is that a UIVIewController should be used to control a full screen of data, because
It is designed to handle rotation. Only one UIViewController should be on the screen at once because only the newest will be notified that the rotation occurred.
To stay true to the MVC paradigm, your business logic should live in a UIViewController, while only display and interaction logic should live in a UIView. Presumably business logic relates to what's on the entire screen.
My question is, given this, how do I structure an app with left-right paging between several top-level views?
I want to create an interface with several UITableViews holding a list of recipes. The user swipes left and right to navigate between lists. There is a common menu on the bottom that stays fixed no matter what.
My initial thought is to use one UIVIewController for the UIScrollView, then add subviews from there.
However I'd really like each list to have its own UIViewController, so it can handle its own rotation, and hold business logic like its own REST methods. It would seem to be a pain to have the top-level UIViewController handle the rotation of a child subview, and it would seem to be a violation of MVC to put the logic anywhere else.
Is there a way to structure an app so that multiple UIViewControllers live inside a UIScrollView, or would it appropriate to use a series of top-level UIViewControllers and UISwipeGestureRecognizer to simulate the paging effect of the UIScrollView?
Thanks.

A couple of thoughts:
If targeting iOS 5 and higher, I'd suggest using a UIPageViewController which is designed for precisely this UI (swiping back and forth between UIViewController instances). You would then have a separate UIViewController for each recipe. In iOS 5, you only have UIPageViewControllerTransitionStylePageCurl transition style, but in iOS 6, you also have UIPageViewControllerTransitionStyleScroll.
For more information, see the Page View Controller section of the View Controller Catalog for iOS.
This is much simpler than writing your own scroll view based solution. If you "roll your own" with a UIScrollView, you'll want to remove instances that have scrolled off screen (by registering as the scroll view's delegate and responding to scrollViewDidScroll) so you don't use up memory unnecessarily.
If you do add child view controllers to your scroll view, don't forget to call the appropriate custom container calls. Specifically, as you add view controllers to your scroll view, make sure you call the following (assuming controller is the child controller and self is the main view controller):
[self addChildViewController:controller];
[self.scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self];
And as you respond to scrollViewDidScroll to remove view controllers that are no longer visible, do the appropriate removal calls, e.g.:
[controller willMoveToParentViewController:nil];
[controller.view removeFromSuperview];
[self removeChildViewController:controller];
For information about why it's important to call these custom container calls, see WWDC 2011 video Implementing UIViewController Containment.
I definitely wouldn't recommend using a UINavigationController, though, because it will keep all of the previous pages in memory.

I believe for the requirements you're talking about you could use a UINavigationController. It will give you the "left-right" paging that you want and you can use a UIViewController for each of your recipes.
Also, I think you want to use a UIScrollView because it lets you perform a "swipe" gesture. If that's the case you could also add a UISwipeGestureRecognizer to your view controllers and every time the gesture is recognized call pushViewController:animated: and popViewControllerAnimated: to perform navigation between your recipes.
This is just and idea.
Hope this helps!

As far as I see, there are 2 good options:
Using a root UINavigationController and push/pop child
ViewControllers depending on the direction of the swipe gesture
(recognized by UISwipeGestureRecognizer, just as you said).
Using a root UIViewController with a UIScrollView and adding the child
viewcontroller views as subviews of the scrollview. To handle
orientation changes, you could pass the orientation-change
UIViewController methods (willRotateToInterfaceOrientation,
didRotateFromInterfaceOrientation) to the child controllers, so they
can handle them.
I hope I helped

Firstly, you should be aware that since iOS 5 it's been possible to include child view controllers inside your main view controller.
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/CreatingCustomContainerViewControllers/CreatingCustomContainerViewControllers.html
So it seems to me that a good options would be to have a paged UIScrollView as your main controller, and then to put instances of your child controller onto each page.
Since that could all be a little memory intensive, you should really only have three instances at any one time (one being displayed, and one one either side so that they're ready if the user starts to scroll). The Apple demo project shows you how to configure a scroll view like that:
https://developer.apple.com/library/ios/samplecode/PageControl/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007795
Hope that helps.

Related

Display new iOS UIView everywhere in existing app [duplicate]

I am subclassing UIApplication to intercept and display touches in my TouchDisplay view. I would like to extend the Application, Window, Delegate or Main ViewController in order to keep my TouchDisplay view on top of all other views. As my and most other applications work, views and controllers are added and removed all the time. I figure the correct answer will be able to deal with these additions and removals and stil keep the TouchDisplay view on top.
Thanks for your help,
Joe
Here are a few approaches you could take for this:
If you're targeting iOS 5+ and iPad only, you can make a top-level view controller which has two contained view controllers. The first would be a view controller for your "TouchDisplay" view. The second would be the application's normal root view controller. (i.e. your existing main view controller; you'll need to set definesPresentationContext to YES on this view controller) Since you're writing the container view controller, you can order those two subviews however you like. There is a WWDC 2011 Talk on view controller containment that goes into great detail about this. This is the most "correct" approach IMHO, because it gives you a view controller for your TouchDisplay view, handles rotation and generally plays nice with others. (This only works on iPad, because on iPhone a new modal view always covers the full screen.)
A more straight-forward approach is to simply add your TouchView to your existing top-level UIWindow as a subview with addSubview:. Most applications don't actually remove the top-level view controller or add new top-level ones; they just present other view controllers from it. A view you add in the top-level window will stay above those. Of course, your app may not follow this rule, in which case you might try option #3 instead. This has rotation gotchas (your view will not auto-rotate when the device rotates, so you need to do this yourself.) You could also force your view back to top, say, on a 1-second timer, if you are having issues with other things covering it. This is also not as nice as option #1 because you don't get a UIViewController, just a UIView.
The most extreme approach is that you can create another UIWindow and give it a higher window level, such as UIWindowLevelAlert and put your TouchDisplay view in that. You can then make the window background transparent, and it will stay above your normal app content. There are lots of gotchas here, especially about auto-rotation and which window is the keyWindow (which is why you should use #1 or #2 instead if you can).
After some time I was able to get my app working. I have made an easy to use overlay that shows touch feedback over your existing application.
You can download the project here:
https://github.com/megaplow/FingerTracks/tree/master/FingerTracks
Happy coding,
Joe

Best way of handling pan gestures

I want to handle transitions between two instances of the same view controller class, using pan gestures.
I've read almost every transitions can be handled by segues, but is it the same when using pan gestures?
Segues don't seem to be a good fit for this kind of transitions.
For the moment, I have a master view controller which instantiates to sub view controllers. The gesture related code resides in the master view controller.
It works well, but I believe there has to be a solution where all the controller management stuff is done in the storyboard.
What is the best place in my code to handle this kind of transitions?
It sounds to me like what you want is a UIPageViewController set up to scroll instead of page curl (That's a settable property) A page view controller would do all the work for you.
There is a sample app in the Xcode docs called PhotoScroller that shows how to set this up. It does lots of other stuff too, (pinch to zoom and image tiling) but you can ignore that.
If you can't get a UIPageViewController to give you the transition you want then you might need to build your own custom parent view controller class. Embedding a single child using a container VC and an embed segue is trivial. I haven't tried to embed multiple children in the same container yet. using embed segues yet. I've done transitions between child VCs using the "manual" parent/child VC calls that were added in iOS 5
I've found a really great article on the way to implement custom transitions with iOS 7, whether they're interactive or not: http://www.captechconsulting.com/blog/tyler-tillage/ios-7-tutorial-series-custom-navigation-transitions-more
It comes with a very detailed demo.
Animations should be classes implementing the UIViewControllerAnimatedTransitioning or UIViewControllerInteractiveTransitioning protocols.
The code using the gesture recognizer can reside in those classes.
The animation can then be returned in the following methods of your navigation controller delegate:
navigationController:animationControllerForOperation:fromViewController:toViewController:
navigationController:interactionControllerForAnimationController:
This way I can plug the animation to whatever controller I want to.
I hope it could help someone.

Need advice regarding multiple child view controllers

All,
I am running into some performance/memory issues when using several ChildViewControllers in my ParentViewController. Here is my situation: I have a ParentViewController with a dynamic number of ChildViewControllers - some times as many as 20. They are contained in a UIScrollView, and are paged through. I'm running into problems when I have several on the page (I'm only loading the first two, then building the others as I swipe), however, having that many within the ParentViewController is starting to cause some crashes due to memory.
The ChildViewController has a lot going on in it, and I'm going through it to make sure it's as efficient as possible, however, I have concerns about this approach working on older devices (as is, I'm getting crashes on the 5S).
It seems that it would help to change the view controllers to just be views, but it'd be a pretty large endeavor as the VC is complex. One suggestion I had was to create a view from the existing view controller's view, and set several delegate methods on the view and interact with the views from the ParentViewController that way. Does any one have any thoughts on the efficiency of that method as a opposed to the current method of using ChildViewControllers?
Another thought I had was to build a custom ContainerViewController and have all the children in there to swipe through, but I wasn't sure if that would give me an advantage over using the children in a UIScrollView.
Any thoughts?
I personally would not advocate the refactoring of your code to use views rather than view controllers. The view controller, itself, is unlikely to be the source of the memory problems, but rather the model objects they keep track of (as well as the assets the view controller's view uses). I think the key is to simply remove the view controllers (and their views) as they scroll off of the screen.
In your scrolling logic, as you're adding child view controllers that scroll into view, you are presumably doing all of the appropriate containment calls:
UIViewController *newChildViewController = ...
[self addChildViewController:newChildViewController];
newChildViewController.view.frame = ...;
[self.scrollView addSubview:newChildViewController.view];
[newChildViewController didMoveToParentViewController:self];
(See WWDC 2011 video Implementing UIViewController Containment for a discussion about why it's important to do these containment calls, namely to keep your view controller hierarchy synchronized with your view hierarchy.)
As the child views scroll out of view, you just do the appropriate containment calls to remove the child controller (and its view):
[childViewControllerToRemove willMoveToParentViewController:nil];
[childViewControllerToRemove.view removeFromSuperview];
[childViewControllerToRemove removeFromParentViewController];
// also remove any other strong references you have to that childViewControllerToRemove
Alternatively, you might want to contemplate using a UIPageViewController which (in iOS 6+) offers scrolling page view (UIPageViewControllerTransitionStyleScroll) for the transitionStyle. This simplifies the amount of custom container code you have to write to handle the view controllers that scroll in and out of view. The UIPageViewController is designed precisely for this situation of scrolling (or paging) through a bunch of different view controllers' views. See the Page View Controllers discussion in the View Controller Catalog for iOS.
I don't think moving them all to UIView's will help. You could achieve this same effect by just adding aChildViewController.view without ever technically adding it as a childView. I would optimize the loading of the views into your UIScrollView. Make sure you only have, say 4 views loaded into memory at any one time. Another option would be to use a horizontal UITableView or UICollectionView so you can reap the memory management features they have build in.
You could turn it into a tableview and let the cell dequeue take over. You would only have a few in memory at a time then.

Keep a UIView or UIViewController on top of all others

I am subclassing UIApplication to intercept and display touches in my TouchDisplay view. I would like to extend the Application, Window, Delegate or Main ViewController in order to keep my TouchDisplay view on top of all other views. As my and most other applications work, views and controllers are added and removed all the time. I figure the correct answer will be able to deal with these additions and removals and stil keep the TouchDisplay view on top.
Thanks for your help,
Joe
Here are a few approaches you could take for this:
If you're targeting iOS 5+ and iPad only, you can make a top-level view controller which has two contained view controllers. The first would be a view controller for your "TouchDisplay" view. The second would be the application's normal root view controller. (i.e. your existing main view controller; you'll need to set definesPresentationContext to YES on this view controller) Since you're writing the container view controller, you can order those two subviews however you like. There is a WWDC 2011 Talk on view controller containment that goes into great detail about this. This is the most "correct" approach IMHO, because it gives you a view controller for your TouchDisplay view, handles rotation and generally plays nice with others. (This only works on iPad, because on iPhone a new modal view always covers the full screen.)
A more straight-forward approach is to simply add your TouchView to your existing top-level UIWindow as a subview with addSubview:. Most applications don't actually remove the top-level view controller or add new top-level ones; they just present other view controllers from it. A view you add in the top-level window will stay above those. Of course, your app may not follow this rule, in which case you might try option #3 instead. This has rotation gotchas (your view will not auto-rotate when the device rotates, so you need to do this yourself.) You could also force your view back to top, say, on a 1-second timer, if you are having issues with other things covering it. This is also not as nice as option #1 because you don't get a UIViewController, just a UIView.
The most extreme approach is that you can create another UIWindow and give it a higher window level, such as UIWindowLevelAlert and put your TouchDisplay view in that. You can then make the window background transparent, and it will stay above your normal app content. There are lots of gotchas here, especially about auto-rotation and which window is the keyWindow (which is why you should use #1 or #2 instead if you can).
After some time I was able to get my app working. I have made an easy to use overlay that shows touch feedback over your existing application.
You can download the project here:
https://github.com/megaplow/FingerTracks/tree/master/FingerTracks
Happy coding,
Joe

UIViewControllers in UIScrollView?

I have a design question. I have a scroll view that I want to use as a means of navigation. I.e. the user can slide between screens. At present, I am creating view controllers and putting their views directly on the scroll view:
aViewController.view.frame=CGRectMake(0,0,320,200);
[self.myScrollView addSubview:[aViewController view]]
Etc, for all 3 view controllers.
I know this isn't best practice. But I can't think of any other way of doing it. Ideally I would like to get the same behaviour as a navigation controller except the fact that I'll be using a scroll view. Any help would be greatly appreciated.
You have to be aware that this breaks certain UIViewController behaviors because these child view controllers do not get informed about interface rotation events and their viewWill/Did(Dis)Appear: methods will not work.
The alternative (until iOS 4.x at least) is to not use UIViewController subclasses for these subviews. You could easily create your own custom controller class (derived from NSObject) to manage a child view.
It's debatable whether that approach makes it easier overall, though.
I'm guessing what you're trying to achieve is something similar to this app?
http://itunes.apple.com/us/app/fanhattan-for-ipad/id436928538?mt=8
This app works in the same manner, using a scroll view for navigation. You can analyze that app, you'd get some ideas from it, i'm sure.

Resources