PDA

View Full Version : Views & view controllers - what is the correct way to structure?




MickeyT
Aug 5, 2012, 05:47 PM
Hello

Following my last post, I have been trying to learn how to transition between views without using storyboard. I have been successful with the following two practice projects - this a breakdown of the structure:

1. Multiple views without a navigation controller

- CustomViewController1 created in AppDelegate

- CustomViewController1's view contains a toolbar with 2 buttons on it

- CustomViewControllers 2 and 3 created as IBOutlets in .xib file of CustomerViewController1

- insertSubView and removeFromSuperView methods used in ViewController1's .m file to load and unload CustomerViewControllers 2 and 3's views (by linking the toolbar buttons to actions)

This works fine.

2. NavigationController app

- NavigationController created in AppDelegate

- CustomTableViewController created in AppDelegate, passed to NavigationController using initWithRootViewController method, and then displayed using addSubView method.

- CustomTableViewController .xib contains a tableview and a CustomViewController as the detail view, which is linked to the CustomTableViewController via IBOutlet

Everything loads fine, and when didSelectRowAtIndexPath method is activated, I use pushViewController method with the CustomViewController property to load the detail view and it works fine.



So, my problem arose when I tried to mix the two together. I wanted to have a main menu screen with a button that, when pressed, would launch a navigation controller. Essentially, example 1 above for the menu and example 2 thereafter.

I have done the following:

- CustomViewController1 is loaded in AppDelegate and has just a completely empty view

- CustomViewController1's .xib file contains a MenuViewController linked via IBOutlet. MenuViewController is loaded by [self. view addSubView] method

- MenuViewController's view has a button on it. A navigation controller is declared within MenuViewController and, when the button is pressed, a CustomTableViewController as in example 2 is created, given to the navigation controller via initWithRootViewController, and then the rest is as example 2.

At this point, MenuViewController's view is a sub view of CustomViewController's view, the navigation controller's view is a sub view of MenuViewController's view and CustomTableViewController's view is the root view of the navigation controller.




I get "unrecognized selector sent to instance" errors on the pressing of the button.

When I moved the navigation controller stuff into CustomViewController1 instead, I got the same error for the cellForRowAtIndexPath method on scrolling the table view.

The cellForRowAtIndexPath method seems to get called when the CustomTableViewController is first created, but then it crashes when it is called again when I scroll some of the cells off the top of the screen.

Lastly, when clicking on a table view cell, the detail view doesn't load - the cell just highlights and nothing happens.

From trawling the internet most of this afternoon, I think it is maybe a problem with things getting released when they shouldn't be and so are not there to deal with some of the method calls.

If anyone can explain what is happening or, if what I am doing is totally wrong, explain simply the correct way for transitioning between different views that have their own view controllers then I'd be grateful.

Thank you and sorry for writing so much but I don't know which part is the problem.



chown33
Aug 5, 2012, 06:54 PM
I get "unrecognized selector sent to instance" errors on the pressing of the button.


Post actual error messages. Complete text. So we can see the class being targeted and the message being sent.

Also post whether you're using ARC or not. If you're not using ARC, and haven't read the memory management guide (https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html), you need to. It you are using ARC, you should read the guide anyway, so you understand what ARC is doing on your behalf.


From trawling the internet most of this afternoon, I think it is maybe a problem with things getting released when they shouldn't be and so are not there to deal with some of the method calls.

"Things getting released when they shouldn't" is either an under-retain or an over-release. Both are memory management errors, and both can usually be found by enabling zombies and seeing what messages get sent to the zombie objects. You can google enable zombies ios and find more information.

An under-retain occurs when you store an object into a variable without claiming ownership. You then use that variable some time later, after it's been dealloc'ed and its memory is then reused to make another object of a completely different class.

An over-release occurs when you release an object you don't own. In my experience, these are less common than under-retains. The result is the same however: the object is dealloc'ed and its memory is then reused to make another object of a completely different class.

If you don't know what object ownership means, read the memory management guide.

dejo
Aug 5, 2012, 10:26 PM
1. Multiple views without a navigation controller

- CustomViewController1 created in AppDelegate

- CustomViewController1's view contains a toolbar with 2 buttons on it

- CustomViewControllers 2 and 3 created as IBOutlets in .xib file of CustomerViewController1

- insertSubView and removeFromSuperView methods used in ViewController1's .m file to load and unload CustomerViewControllers 2 and 3's views (by linking the toolbar buttons to actions)

Sounds like you are presenting 2 and 3 as modal views. Why are you not using the modal view presentation methods?

MattInOz
Aug 5, 2012, 11:14 PM
Why mix the two?
Why not just hide the Navigation Bar when you don't want it?

MickeyT
Aug 6, 2012, 03:35 AM
Why mix the two?
Why not just hide the Navigation Bar when you don't want it?

I didn't realise you could do that, and that would certainly solve the issue.

However, I decided to try this to see if what I thought I understood about views and view controllers was the case. The first two examples are from a book and from an internet site. From these I thought I had worked out how transitioning from one view to the other worked, which is:

a) A view must be paired with a view controller

b) A view is loaded into a window in the AppDelegate, and control passes to that view's view controller via the viewDidLoad method (call it VC1)

c) If VC1 has the responsibility of loading another view, a second view and view controller (VC2) are created, and an instance of VC2 is created in VC1's .xib file so that you can refer to it via an IBOutlet declaration (by creating a parent controller and selecting the custom class from the drop down list). The following would load the view of VC2:

[VC1.view insertSubView:VC2.view atIndex;0]; (That's from memory as I'm at work, so sorry if the syntax is not quite right)

d) As the view is loaded at index 0, it is the view that is foremost and therefore appears on the screen

e) If VC2 wants to load a sub view, it does so as in c) but with, say, VC3.

f) If VC3's view needs to close, I could call:

[self.view removeFromSuperView];

and VC3's view would be removed and it would effectively "kill" itself (as I have ARC enabled) and hand back control to VC2's view controller just after the point where VC3 was originally created.

So, at this point I have a hierarchy of sub views where VC1 has loaded a sub view of a VC2 view, which in turn has loaded a sub view of a VC3 view. On loading a view, that view's view controller has control until I discard the view, at which point I thought control would pass back to the method in the next view controller up where the view was loaded.

Am I completely wrong on this? I just thought that it can't be the case that every app in the world that has multiple views uses a navigation controller and so I wanted to understand how to shuffle things around manually first before using the free functionality a navigation controller offers.

Also post whether you're using ARC or not. If you're not using ARC, and haven't read the memory management guide, you need to. It you are using ARC, you should read the guide anyway, so you understand what ARC is doing on your behalf.


I did leave ARC ticked, but I will read the guide - thank you.

Post actual error messages. Complete text. So we can see the class being targeted and the message being sent.

I will do so when I get in from work - sorry.

Why are you not using the modal view presentation methods?

What does that mean - I have been loading views using code from the examples in the book I have (Sams Teach Yourself iPhone Applications) so I don't think I've come across modal?

Thank you all for responding.

dejo
Aug 6, 2012, 08:46 AM
What does that mean - I have been loading views using code from the examples in the book I have (Sams Teach Yourself iPhone Applications) so I don't think I've come across modal?

Two things:

1) Get a different book. "Beginning iOS 5 Development: Exploring the iOS SDK" by David Mark, Jack Nutting, and Jeff LaMarche or "iOS Programming: The Big Nerd Ranch Guide" by Joe Conway and Aaron Hillegass are both highly recommended around here. I've heard nothing but bad things about the "SAMS Teach Yourself" books for iOS development. They seem to like doing things the non-standard way.

2) Take a look at the "View Controller Programming Guide for iOS", especially the section on "Presenting View Controllers from Other View Controllers" (http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html#//apple_ref/doc/uid/TP40007457-CH111-SW1).

MickeyT
Aug 6, 2012, 05:32 PM
Two things:

2) Take a look at the "View Controller Programming Guide for iOS", especially the section on "Presenting View Controllers from Other View Controllers" (http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html#//apple_ref/doc/uid/TP40007457-CH111-SW1).

Thank you! After reading the article my app now works.

I think I also spotted a mistake in my AppDelegate too - I wasn't assigning a root view controller to self.window at any point. I think this was probably what was causing my "unrecognised selector sent to instance" errors because I don't get any at all now after changing that.

Just one last quick question:

When you create views and view controllers in this way, is it absolutely best practice to create a delegate and have the presenting view controller dispose of the presented one? Could the presented view controller remove itself instead, which would mean not having to bother with delegates and protocols?

For example, in the presented view controller, could I put the following instead of calling the protocol method to get the presenting view controller to dismiss it's presented view:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

Seems to work, but just want to be doing and learning what is deemed to be best practice as it does say in the Apple guide that presenting controllers should take care of their presented ones. Just seems like the protocol stuff is a bit of extra trickiness just to get a view to disappear.

Thank you again for that link, and I'm going to buy the Big Nerd Ranch guide and see how I go with that.

dejo
Aug 7, 2012, 12:37 PM
I think I also spotted a mistake in my AppDelegate too - I wasn't assigning a root view controller to self.window at any point. I think this was probably what was causing my "unrecognised selector sent to instance" errors because I don't get any at all now after changing that.
If your app didn't crash, I believe you would have received a run-time warning:
Application windows are expected to have a root view controller at the end of application launch


Just one last quick question:

When you create views and view controllers in this way, is it absolutely best practice to create a delegate and have the presenting view controller dispose of the presented one? Could the presented view controller remove itself instead, which would mean not having to bother with delegates and protocols?

For example, in the presented view controller, could I put the following instead of calling the protocol method to get the presenting view controller to dismiss it's presented view:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

Seems to work, but just want to be doing and learning what is deemed to be best practice as it does say in the Apple guide that presenting controllers should take care of their presented ones. Just seems like the protocol stuff is a bit of extra trickiness just to get a view to disappear.

Thank you again for that link, and I'm going to buy the Big Nerd Ranch guide and see how I go with that.

You certainly can just dismiss the viewController without delegation (and it works). Even this works:
[self dismissViewControllerAnimated:YES completion:nil];

But if Apple says an approach is the preferred one, then, yes, most of the time that is the best practice. I'm not sure if you made it onto the section in the doc titled "Using Delegation to Communicate with Other Controllers" (http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html#//apple_ref/doc/uid/TP40007457-CH8-SW9) but it adds some reasoning behind the "madness":
Using delegation to manage interactions with other app objects has key advantages over other techniques:


The delegate object has the opportunity to validate or incorporate changes from the view controller.
The use of a delegate promotes better encapsulation because the view controller does not have to know anything about the class of the delegate. This enables you to reuse that view controller in other parts of your app.


Hope that helps.

MickeyT
Aug 7, 2012, 12:57 PM
But if Apple says an approach is the preferred one, then, yes, most of the time that is the best practice. I'm not sure if you made it onto the section in the doc titled "Using Delegation to Communicate with Other Controllers" (http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ManagingDataFlowBetweenViewControllers/ManagingDataFlowBetweenViewControllers.html#//apple_ref/doc/uid/TP40007457-CH8-SW9) but it adds some reasoning behind the "madness":

I did read that part, and amended my app to work with a delegate initially and everything worked fine.

However, I was looking at it and thinking that if there was just a way of talking to the presenting view controller I would be able to call the dismiss method that way, and realised I did have access via the presentingViewController property.

Interesting that it can be called on itself as well.

So just to absolutely clarify (sorry) - does that mean that for all custom view controller classes that I ever create I should implement at least one protocol method (i.e. the method to dismiss it) so that it can be dismissed properly?

And absolutely last last last question - is this approach and the stack-based approach of the navigation controller pretty much the only two ways views are loaded and unloaded (segues aside - I'll worry about pre-storyboard stuff for the moment) - there aren't any more completely distinct ways that I should look at?

Thank you very much for your help - thoughts of giving up did momentarily flash through my head at one point over the weekend but you've really helped.

dejo
Aug 8, 2012, 09:05 AM
However, I was looking at it and thinking that if there was just a way of talking to the presenting view controller I would be able to call the dismiss method that way, and realised I did have access via the presentingViewController property.

Interesting that it can be called on itself as well.

Yes, using the presentingViewController, or even the viewController itself, is possible, and works. But is it recommended? Not by Apple. But your app probably won't be rejected for doing it that way. Heck, there are quite a few books and tutorials out there that use [self dismissViewController...]

So just to absolutely clarify (sorry) - does that mean that for all custom view controller classes that I ever create I should implement at least one protocol method (i.e. the method to dismiss it) so that it can be dismissed properly?

Not necessarily. If your custom view controller will be part of a navigation stack, for example, you don't need delegation since you will be using [navigationController popViewControllerAnimated:] instead of [presentingViewController dismissViewControllerAnimated:completion:]

And absolutely last last last question - is this approach and the stack-based approach of the navigation controller pretty much the only two ways views are loaded and unloaded (segues aside - I'll worry about pre-storyboard stuff for the moment) - there aren't any more completely distinct ways that I should look at?
Yes, there are others. If you look through the View Controller Programming Guide, you'll see there's also UIPageViewController, UISplitViewController, UITabBarController, and UITableViewController, as well as UIPopoverController for the iPad (and, in iOS 6, UICollectionViewController).

Duncan C
Aug 8, 2012, 03:21 PM
Yes, using the presentingViewController, or even the viewController itself, is possible, and works. But is it recommended? Not by Apple. But your app probably won't be rejected for doing it that way. Heck, there are quite a few books and tutorials out there that use [self dismissViewController...]



Not necessarily. If your custom view controller will be part of a navigation stack, for example, you don't need delegation since you will be using [navigationController popViewControllerAnimated:] instead of [presentingViewController dismissViewControllerAnimated:completion:]


Yes, there are others. If you look through the View Controller Programming Guide, you'll see there's also UIPageViewController, UISplitViewController, UITabBarController, and UITableViewController, as well as UIPopoverController for the iPad (and, in iOS 6, UICollectionViewController).


And in iOS >= 5.0, the new parent/child view controller additions to UIViewController. That allows you to build your own container view controllers like navigation controllers, tab bar controllers, etc.

dejo
Aug 8, 2012, 06:29 PM
And in iOS >= 5.0, the new parent/child view controller additions to UIViewController. That allows you to build your own container view controllers like navigation controllers, tab bar controllers, etc.
Oops, forgot about those. My apologies.