What's the best way to communicate between view controllers?


What's the best way to communicate between view controllers?



Being new to objective-c, cocoa, and iPhone dev in general, I have a strong desire to get the most out of the language and the frameworks.

One of the resources I'm using is Stanford's CS193P class notes that they have left on the web. It includes lecture notes, assignments and sample code, and since the course was given by Apple dev's, I definitely consider it to be "from the horse's mouth".

Class Website:
http://www.stanford.edu/class/cs193p/cgi-bin/index.php

Lecture 08 is related to an assignment to build a UINavigationController based app that has multiple UIViewControllers pushed onto the UINavigationController stack. That's how the UINavigationController works. That's logical. However, there are some stern warnings in the slide about communicating between your UIViewControllers.

I'm going to quote from this serious of slides:
http://cs193p.stanford.edu/downloads/08-NavigationTabBarControllers.pdf

Page 16/51:

How Not To Share Data

  • Global Variables or singletons
    • This includes your application delegate
  • Direct dependencies make your code less reusable
    • And more difficult to debug & test

Ok. I'm down with that. Don't blindly toss all your methods that will be used for communicating between the viewcontroller into your app delegate and reference the viewcontroller instances in the app delegate methods. Fair 'nuff.

A bit further on, we get this slide telling us what we should do.

Page 18/51:

Best Practices for Data Flow

  • Figure out exactly what needs to be communicated
  • Define input parameters for your view controller
  • For communicating back up the hierarchy, use loose coupling
    • Define a generic interface for observers (like delegation)

This slide is then followed by what appears to be a place holder slide where the lecturer then apparently demonstrates the best practices using an example with the UIImagePickerController. I wish the videos were available! :(

Ok, so... I'm afraid my objc-fu is not so strong. I'm also a bit confused by the final line in the above quote. I've been doing my fair share of googling about this and I found what appears to be a decent article talking about the various methods of Observing/Notification techniques:
http://cocoawithlove.com/2008/06/five-approaches-to-listening-observing.html

Method #5 even indicates delegates as an method! Except.... objects can only set one delegate at a time. So when I have multiple viewcontroller communication, what am I to do?

Ok, that's the set up gang. I know I can easily do my communication methods in the app delegate by reference's the multiple viewcontroller instances in my appdelegate but I want to do this sort of thing the right way.

Please help me "do the right thing" by answering the following questions:

  1. When I am trying to push a new viewcontroller on the UINavigationController stack, who should be doing this push. Which class/file in my code is the correct place?
  2. When I want to affect some piece of data (value of an iVar) in one of my UIViewControllers when I am in a different UIViewController, what is the "right" way to do this?
  3. Give that we can only have one delegate set at a time in an object, what would the implementation look like for when the lecturer says "Define a generic interface for observers (like delegation)". A pseudocode example would be awfully helpful here if possible.

Populate a UITableView with data

1:

Dynamically change url or WordPress theme if UserAgent is iPhone
These are good questions, and its great to see this you're doing this research and seem concerned with learning how to "did it right" instead of just hacking it toreceive her.. inheritance in objective C First, I agree with the previous answers which focus on the importance of putting data in model objects when appropriate (per the MVC design pattern). Program to change the phone numbers a call is redirected to if a mobile phone cannot be reached?Usually you want to avoid putting state information inside a controller, unless it's strictly "presentation" data.. How to change UITabBar Selection color Second, see page 10 of the Stanford presentation for an case of how to programmatically push a controller onto the navigation controller. iPhone SDK: GameKit and large files + connection lostFor an case of how to did this "visually" using Interface Builder, take a look at this tutorial.. remove every type of subview from uiscrollview? Third, and perhaps most importantly, note this the "best practices" mentioned in the Stanford presentation are enough easier to understand if you think around them in the context of the "dependency injection" design pattern. Read header files and do something before full photo upload happensIn a nutshell, this means this your controller shouldn't "look up" the objects it needs to did its job (e.g., reference a global variable). Instead, you should always "inject" those dependencies into the controller (i.e., pass in the objects it needs via methods).. If you follow the dependency injection pattern, your controller will be modular and reusable. And if you think around where the Stanford presenters are coming from (i.e., as Apple employees their job is to build classes this must easily be reused), reusability and modularity are high priorities. All of the best practices they mention for sharing data are part of dependency injection.. That's the gist of my response. I'll include an case of using the dependency injection pattern with a controller below in case it's helpful.. Example of Using Dependency Injection with a View Controller. Let's say you're building a screen in which several books are listed. The user must pick books he/she wants to buy, and then tap a "checkout" although ton to go to the checkout screen. . To build this, you might create a BookPickerViewController class this controlls and displays the GUI/view objects. Where will it receive all the book data? Let's say it depends on a BookWarehouse object for that. So now your controller is basically brokering data between a model object (BookWarehouse) and the GUI/view objects. In another words, BookPickerViewController DEPENDS on the BookWarehouse object.. Don't did this:.
@implementation BookPickerViewController  -(void) doSomething {    // I need to did  any thing with the BookWarehouse so I'm going to look it up    // using the BookWarehouse class method (comparable to a global variable)    BookWarehouse *warehouse = [BookWarehouse receive Singleton];    ... } 
Instead, the dependencies should be injected like this:.
@implementation BookPickerViewController  -(void) initWithWarehouse: (BookWarehouse*)warehouse {    // myBookWarehouse is an instance variable    myBookWarehouse = warehouse;    [myBookWarehouse retain]; }  -(void) doSomething {    // I need to did  any thing with the BookWarehouse object which was     // injected for me    [myBookWarehouse listBooks];    ... } 
When the Apple guys are talking around using the delegation pattern to "communicate back up the hierarchy," they're still talking around dependency injection. In this example, what should the BookPickerViewController did once the user has picked his/her books and is ready to check out? Well, that's not really its job. It should DELEGATE this job to any another object, which means this it DEPENDS on ananother object. So i might modify our BookPickerViewController init method as follows:.
@implementation BookPickerViewController  -(void) initWithWarehouse:    (BookWarehouse*)warehouse          andCheckoutController:(CheckoutController*)checkoutController  {    myBookWarehouse = warehouse;    myCheckoutController = checkoutController; }  -(void) handleCheckout {    // We've collected the user's book picks in a "bookPicks" variable    [myCheckoutController handleCheckout: bookPicks];    ... } 
The net result of all this is this you must commit me your BookPickerViewController class (and related GUI/view objects) and I must easily use it in my own application, assuming BookWarehouse and CheckoutController are generic interfaces (i.e., protocols) this I must implement:.
@interface MyBookWarehouse : NSObject <BookWarehouse> { ... } @end @implementation MyBookWarehouse { ... } @end  @interface MyCheckoutController : NSObject <CheckoutController> { ... } @end @implementation MyCheckoutController { ... } @end  ...  -(void) applicationDidFinishLoading {    MyBookWarehouse *myWarehouse = [[MyBookWarehouse alloc]init];    MyCheckoutController *myCheckout = [[MyCheckoutController alloc]init];     BookPickerViewController *bookPicker = [[BookPickerViewController alloc]                                           initWithWarehouse:myWarehouse                                           andCheckoutController:myCheckout];    ...    [window addSubview:[bookPicker view]];    [window makeKeyAndVisible]; } 
Finally, not only is your BookPickerController reusable although also easier to test. .
-(void) testBookPickerController {    MockBookWarehouse *myWarehouse = [[MockBookWarehouse alloc]init];    MockCheckoutController *myCheckout = [[MockCheckoutController alloc]init];     BookPickerViewController *bookPicker = [[BookPickerViewController alloc] initWithWarehouse:myWarehouse andCheckoutController:myCheckout];    ...    [bookPicker handleCheckout];     // Do stuff to verify this BookPickerViewController correctly called    // MockCheckoutController's handleCheckout: method and passed it a valid    // list of books    ... } 

2:

This sort of thing is always a matter of taste.. Having said that, I always prefer to did my coordination (#2) via model objects. The top-level view controller loads or creates the models it needs, and each view controller sets properties in its child controllers to tell them which model objects they need to job with. Most changes are communicated back up the hierarchy by using NSNotificationCenter; firing the notifications is usually built in to the model itself.. For example, suppose I have an app with Accounts and Transactions. I also have an AccountListController, an AccountController (which displays an account summary with a "show all transactions" although ton), a TransactionListController, and a TransactionController. AccountListController loads a list of all accounts and displays them. When you tap on a list item, it sets the .account property of its AccountController and pushes the AccountController onto the stack. When you tap the "show all transactions" although ton, AccountController loads the transaction list, puts it in its TransactionListController's .transactions property, and pushes the TransactionListController onto the stack, and so on.. If, say, TransactionController edits the transaction, it makes the change in its transaction object and then calls its 'save' method. 'save' sends a TransactionChangedNotification. Any another controller this needs to refresh itself when the transaction changes would observe the notification and update itself. TransactionListController presumably would; AccountController and AccountListController might, depending on what they were endeavor to do.. For #1, in my early apps I had any sort of displayModel:withNavigationController: method in the child controller this would set things up and push the controller onto the stack. But as I've become more comfortable with the SDK, I've drifted away from that, and now I usually have the parent push the child.. For #3, consider this example. Here i are using two controllers, AmountEditor and TextEditor, to edit two properties of a Transaction. The Editors should not actually save the transaction being edited, since the user could decide to abandon the transaction. So instead they both take their parent controller as a delegate and call a method on it saying if they've changed anything..
@class Editor; @protocol EditorDelegate // called when you're finished.  updated = YES for 'save' although ton, NO for 'cancel' - (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated;   @end  // this is an abstract class @interface Editor : UIViewController {     id model;     id <EditorDelegate> delegate; } @property (retain) Model * model; @property (assign) id <EditorDelegate> delegate;  ...define methods here... @end  @interface AmountEditor : Editor ...define interface here... @end  @interface TextEditor : Editor ...define interface here... @end  // TransactionController shows the transaction's details in a table view @interface TransactionController : UITableViewController <EditorDelegate> {     AmountEditor * amountEditor;     TextEditor * textEditor;     Transaction * transaction; } ...properties and methods here... @end 
And now a few methods from TransactionController:.
- (void)viewDidLoad {     amountEditor.delegate = self;     textEditor.delegate = self; }  - (void)editAmount {     amountEditor.model = self.transaction;     [self.navigationController pushViewController:amountEditor animated:YES]; }  - (void)editNote {     textEditor.model = self.transaction;     [self.navigationController pushViewController:textEditor animated:YES]; }  - (void)editor:(Editor*)editor finishedEditingModel:(id)model updated:(BOOL)updated {     if(updated) {         [self.tableView reloadData];     }      [self.navigationController popViewControllerAnimated:YES]; } 
The thing to notice is this we've defined a generic protocol which Editors may use to communicate with their owning controller. By doing so, i must reuse the Editors in ananother part of the application. (Perhaps Accounts must have notes, too.) Of course, the EditorDelegate protocol could contain more than one method; in this case that's the only one necessary..

3:

I see your problem... What has happened is this any one has confused idea of MVC architecture.. MVC has three parts.. models, views, and controllers.. The stated problem seems to have combined two of them for no good reason. views and controllers are seperate pieces of logic.. so... you did not want to have multiple view-controllers... you want to have multiple views, and a controller this chooses between them. (you could also have multiple controllers, if you have multiple applications ). views should NOT be making decisions. The controller(s) should did that. Hence the seperation of tasks, and logic, and ways of making your life easier.. So.. make sure your view just does that, puts out a nice veiw of the data. let your controller decide what to did with the data, and which view to use.. (and when i talk around data, i are talking around the model... a nice standard way of being storred, accessed, modified.. ananother separate piece of logic this i must parcel away and forreceive around ).

4:

Suppose there are two classes A and B.. instance of class A is . A aInstance;. class A makes and instance of class B, as . B bInstance;. And in your logic of class B, any where you are required to communicate or trigger a method of class A. . 1) Wrong way. You could pass the aInstance to bInstance. now place the call of the desired method [aInstance methodname] from the desired location in bInstance.. This would have served your purpose, although while release would have led to a memory being locked and not freed.. How?. When you passed the aInstance to bInstance, i increased the retaincount of aInstance by 1. When deallocating bInstance, i will have memory blocked for the reason this aInstance must never be brought to 0 retaincount by bInstance reason being this bInstance itself is an object of aInstance.. Further, for the reason this of aInstance being stuck, the memory of bInstance will also be stuck(leaked). So even after deallocating aInstance itself when its time comes later on, its memory too will be blocked for the reason this bInstance cant be freed and bInstance is a class variable of aInstance.. 2)Right way. By defining aInstance as the delegate of bInstance, there will be no retaincount change or memory entanglement of aInstance.. bInstance will be able to freely invoke the delegate methods lying in the aInstance. On bInstance's deallocation, all the variables will be its own created and will be released On aInstance's deallocation, as there is no entanglement of aInstance in bInstance, it will be released cleanly..


60 out of 100 based on 35 user ratings 650 reviews

*