Managing multiple view controllers and data - objective-c

My app has a main screen that the user always starts at, and from which I want to display other views programmatically. I set up the app identically to the approach in "Beginning iPhone Development" Ch. 6, which is to use a RootViewController that loads in other view controllers.
The book uses a button to trigger loading the next view controller, but in my app I need to swap controllers at the end of function calls and to share data (processed UIImages, etc) between views. I am not using a tab bar or navigation controller.
What I'm wondering is, should I just make my MainViewController the root controller and presentModalViewControllers from there? I'd like to keep the root model but I don't quite understand how to hook it all up and share data. I've seen posts that mention using protocols and notifications but I haven't wrapped my head around it yet. Any advice is appreciated.

What you want to do is add a Cocoa property in your main view controller that references the object instances which you want to share with subordinate view controllers.
For example, if we want to share an NSArray, we specify its property in the main view controller header:
#interface MainViewController : UIViewController {
NSArray *myArray;
}
#property (nonatomic, retain) NSArray *myArray;
#end
In the implementation, add the #synthesize directive and remember to release the array in -dealloc:
#implementation MainViewController
#synthesize myArray;
...
- (void) dealloc {
[myArray release];
[super dealloc];
}
#end
You also want to add this property to view controllers that are subordinate to the main view controller, in the exact same way. In their headers, specify the same variable name and property description.
In your main view controller, when you are ready to push a subordinate view controller, you set the subordinate view controller's property accordingly just before pushing:
- (void) pushSubordinateViewController {
SubordinateViewController *subVC = [[SubordinateViewController alloc] initWithNibName:#"SubordinateViewController" bundle:nil];
subVC.myArray = self.myArray; // this sets the sub view controller's myArray property
[self.navigationController pushViewController:subVC animated:YES];
[subVC release];
}
Likewise in your subordinate view controller, it will need to set its subordinate's array property accordingly, just before it pushes its own sub-sub-view controller.
By setting references in this way, each view controller is pointed at the same array, containing the desired elements.
To use the array, just call self.myArray, e.g. [self.myArray objectAtIndex:index] to get an object at a given index.

Related

How to access a property declared in a viewController which is assigned to UINavigationController?

I am trying to implement a application which has five tabs totally. Each of tabs corresponds to a view controller, such as viewController1~viewController5.
For the viewController4, I add the navigationController on it in AppDelegate.m as following:
viewController4 = [[iPhone_ASRAViewController alloc] initWithNibName:#"iPhone_ASRAViewController_iPhone" bundle:nil];
navController1 = [[UINavigationController alloc]initWithRootViewController:viewController4];
In the iPhone_ASRViewController class, I have declared a property in .h file as following:
#property (nonatomic, retain) NSString *student_id;
Then, I want to access the student_id(set the student_id) which is declared in iPhone_ASRViewController in the FirstViewController, and implement in FirstViewController.m as following:
iPhone_ASRAViewController *iphone_ASRAVC= [self.tabBarController.viewControllers objectAtIndex:3];
iphone_ASRAVC.student_id=[stu_class stringByAppendingString:stu_id];
//stu_class and stu_id is the text field declared in the FirstViewController.
Ideally, when a certain button which is implemented in the FirstViewController class is pushed by users, the value of student_id will also be set to iPhone_ASRAViewController class.
Unfortunately, the app will crash when users push the button. Error msgs as following:
[UINavigationController setStudent_id:]: unrecognized selector sent to instance 0x9341170
Can someone provide me with some ideas/solutions to debug, please?
Well, wenn the view controllers in a tab bar have (or better are) navigation controllers, then the viewControllers array of the tab bar controller contains navigation controllers instead. Actually, you (or your storyboard) put them in!
It may well be a mix of view controllers and navigation controllers, depending of what was actually set upon creation or reconfiguration (if any) of the tab bar.
So I'd sugest to receive the object from viewContollers at the given index into something of type ID. Then check whether it is a navigation controller (use isKindOfClass:[iPhone_ASRAViewController class]) and if so then use it directly. If not then check whether it is of class UINavigationConroller (or the other way around - what ever is more convenient for you) and if so fetch its topViewController property and go from there.
Edit: Added in response to comments:
id someController = [self.tabBarController.viewControllers objectAtIndex:3];
if (someController isKindOfClass:[UINavigationController class]) {
someController = [someController topViewController]; //re-using someController
}
// someController should be a UIViewController from here onwards. But you may double check if you want.
if (someController isKindOfClass:[iPhone_ASRAViewController class]) {
iPhone_ASRAViewController *myIPhone_ASRAViewController (iPhone_ASRAViewController*) someController;
// you may now savely access those properties that are unique to your custom view controller class
}

How to properly remove a pin from another viewController

What I've done so far is working but I would like to know whether this is the proper way or not.
I have a map that shows an annotation when this is pressed shows a callout.
The next view shown is a table view. This table has a button to remove that annotation.
I created one property in the table View of type MKMapView. After this view is initialized when the callOut accessory is tapped, I set the MKMapView property.
When the button is pressed in the table view, I delete the annotation through the map property.
Is this the right way?
Rather than the detail view directly manipulating the parent (map) controller view's controls, a more "right" approach might be to use delegate+protocol.
Define a protocol with the methods that the map controller needs to implement (eg. deleteAnnotation, detailViewDone, etc).
The detail view will have a delegate property for that protocol and call the protocol methods via the delegate property instead of directly accessing and modifying another view's controls.
The map controller would set itself as the delegate of the detail view and actually implement the protocol methods.
This way, each controller/class doesn't have to know the internal details of how the others work and let's you more easily change how each one works internally without affecting code in the others (as long as the protocol doesn't change). It improves encapsulation and reusability.
For example, in the detail view .h, define the protocol and declare the delegate property:
#protocol DetailViewControllerDelegate <NSObject>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
//could have more methods or change/add parameters as needed
#end
#interface DetailViewController : UIViewController
#property (nonatomic, assign) id<DetailViewControllerDelegate> delegate;
#end
In the detail view .m, wherever you handle the delete button, call the delegate method instead:
if ([delegate respondsToSelector:#selector(deleteAnnotation:)])
{
[delegate deleteAnnotation:annotation];
}
In the map controller .h, declare that it implements the protocol and declare the methods:
#interface MapViewController : UIViewController<DetailViewControllerDelegate>
-(void)deleteAnnotation:(id<MKAnnotation>)annotation;
-(void)detailViewDone;
#end
In the map controller .m, in calloutAccessoryControlTapped where you create the detail view, set the delegate property instead of the map view property:
DetailViewController *dvc = [[DetailViewController alloc] init...
dvc.annotation = view.annotation;
dvc.delegate = self;
[self presentModalViewController:dvc animated:YES];
Finally, also in the map controller .m, implement the delegate method:
-(void)deleteAnnotation:(id<MKAnnotation>)annotation
{
[mapView removeAnnotation:annotation];
//dismiss the detail view (if that's what you want)...
[self dismissModalViewControllerAnimated:YES];
}
From the documentation, the articles Delegates and Data Sources and Using Delegation to Communicate with Other Controllers may be useful as well.

Changes not reflected across view when using binding in cocoa

I am creating some sample applications to understand the concepts of view navigation, binding etc in cocoa.
Here is the scenario:
I have a window that has a tab view(2 tabs) in MainMenu.Xib.
I have a text field in the first tab and label in the second tab. I want both of them to reflect the same value and I want to do this using binding. Also, I don't want to use the views provided to me along with the tab view.
These are the steps I have done.
The view of each tab view item is set separately in the applicationDidFinishLaunching: method using the following code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
//initialize view controllers
view1=[[ViewTab1 alloc] initWithNibName:#"ViewTab1" bundle:nil];
view2=[[ViewTab2 alloc] initWithNibName:#"ViewTab2" bundle:nil];
//set views
[[[myTabView tabViewItems] objectAtIndex:0]setView:view1.view];
[[[myTabView tabViewItems] objectAtIndex:1]setView:view2.view];
}
myTabView is the outlet reference of the tab view from MainMenu.xib in AppDelegate.
ViewTab1 is the name of the first view controller (and the xib).
ViewTab2 is the name of the second view controller (and the xib).
ViewTab1 has one single text field (and an associated label). I have bound this to a variable(name) declared in AppDelegate.
ViewTab2 has a label. I have bound this also to the same variable in AppDelegate.
The variable, 'name' is initialized in the init method of AppDelegate.
AppDelegate.h
....
NSString *name;
....
#property(strong) ViewTab1 *view1;
#property(strong) ViewTab2 *view2;
#property (assign) IBOutlet NSTabView *myTabView;
#property (strong) NSString *name;
....
AppDelegate.m
....
#synthesize myTabView;
#synthesize view1,view2;
#synthesize name;
....
- (id)init {
self = [super init];
if (self) {
name=#"dummy";
}
return self;
....
Apart from this I haven't done any coding in my program.
In the ViewTab1.xib I got an object and made it an instance of AppDelegate and then connected the delegate reference of the Application object(NSApplication) to the same object. (I hope this is the right way of getting the AppDelegate object.)
I did the same in ViewTab2.xib
Then I bound the text field in ViewTab1 and label in ViewTab2 to this variable in AppDelegate.
When I run the program both the text field and label shows "dummy". But when I change the value in the text field, its not reflected in the label in the second tab( i.e. ViewTab2).
Please tell me what I'm doing wrong.
How to establish binding to the same App delegate object from any loaded Nib?
Yes, I know this frustrated situation as described in question... after many weeks and hundreds pages of documentation for KVO - Notifications - Bindings I think there is one very simple solution for that.
As we can find in some information sources the nib-loading process produce new instances of members... and we need to use binding connection to the old one.
Note that bindings made in InterfaceBuilder are redirect to these new instances automatically after loading nib
Why not redirect the pointer of App delegate to the old instance?
In method where you loads your nib you can test which object is app delegate before and just after nib load.
If the new one isn’t the same as the previous one you can redirect it as you want.
This simple example works for me in Xcode3 under 10.5.8 with target to OSX10.5 / i386:
// ***** SOMEWHERE IN DEFAULT APP-DELEGATE.m IMPLEMENTATION
- (IBAction) createOtherWindowFromNib: (id)sender
{
// ensure that app delegate is set as you want...
[NSApp setDelegate:self];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE BEFORE NIB LOAD: %# ", [[NSApp delegate] description]);
// we can bind members of the nib to this controller over proxy object named "File’s Owner"
NSWindowController *otherWinCapo = [[NSWindowController alloc] initWithWindowNibName: #"OtherTestWindow"];
NSLog(#"APP-DELEGAT **** CREATE-TEST-WINDOW ***** WHO IS APP-DELEGATE AFTER NIB LOAD: %# ", [[NSApp delegate] description]);
// make some test for delegates before/after here if you need ...
// usually your bindings made inside "OtherTestWindow.xib" by IB doesn’t works in this moment
// ... and some redirection if needed
[NSApp setDelegate:self];
// afer that the bind made in IB inside "OtherTestWindow.xib"
// referred to (proxy object) "Application.delegate.myBOOL" (Bind to:Application, Model Key Path:delegate.myBOOL)
// react to changes of myBOOL placed in default app delegate object as expected
// simultaneously in every open instance of "OtherTestWindow.xib"
[otherWinCapo showWindow: otherWinCapo.window]; // we need populate the window instance on screen to see it
}
I think the problem is that the objects in your xibs that you set to the app delegate class create 2 different instances of the app delegate, so changing the value of the text field changes the value of name in one instance but not in the other. That's what you're doing wrong, unfortunately, I can't think of a solution at this time.
Have you turned on 'Continuously Updates Value' in the NSTextField controls?
See this example.

Accessing an instance method of a ViewController From Another view controller

Let's say I have a view controller called vc1, which a synthesized property called property1, and i wants to access it from another view controller (vc2) and change it from vc2.
Now the methods created by the #syntisize to change and get properties are instance methods, so how can I get to them fro another view controller (do view controllers have instances in the app, and if so, what are they?)
Just to be clear I am using storyboards, so I never really instantiate the view controllers...
VC1.m:
-(void) yourMethod {
...
}
VC2.m
YOURViewController * vc2 = [[YOURViewController alloc]init];
[vc yourMethod];
[vc release];
Make sure to import your YOURViewController in your other view .m file
Something like that should work.
Or if you're having problems, try this tutorial here:
Tutorial on How-To Pass Data Between Two View Controllers
Hope this helps :)
While you can do it the way you describe, I think the common technique (assuming VC1 has a segue to VC2) is a bit different, where VC2 will have a property that will be set by prepareForSegue. See Configuring the Destination Controller When a Segue is Triggered in the View Controller Programming Guide.
You will need to link the storyboard views with the viewcontrollers so the view for vc1 would use the class vc1 etc for the rest (I assume you have done this because this is important when coding for different views)
Then all you need to do is where ever you are calling the properties so lets say the viewDidLoad method, declare the view controller like this:
- (void) viewDidLoad {
vc1 *viewController;
// Now you change the variable I'll presume its a UILabel so I'll change its text [viewController.property1 setText:#"I changed a different views UILabel"];
}
Let me know whether this works... Its worked for me before so should work

Obj-c, If I'm using getters and setters for a selection screen pushed into navigation, where do I get my value back, viewWillAppear?

I'm using getters and setters on a selection screen, which is pushed onto the navigation stack, by a button on my navigation bar.
The selection screen sets the variables, however I'm not sure where to get the value on my parent screen.
The selection screen is only initiated and defined in my navigation bar button.
Do I have to declare the viewController in my interface of my parent screen ?
There are MANY ways to do this, and over the time I've been working on iPhone software I've experimented with most of them. Here's one.
In your child view controller, you can make a property that is a pointer to the parent view controller. Thusly:
ChildViewController.h
#import "ParentViewController.h"
#interface ChildViewController : UIViewController
{
ParentViewController *parentViewController;
}
#property (nonatomic, retain) ParentViewController *parentViewController;
ChildViewController.m
#implementation ChildViewController
#synthesize parentViewController;
-(void)dealloc
{
[parentViewController release];
}
When you do the work to instantiate the child and push it onto the nav controller, you can set any variables you want on that child's properties, including setting, say:
ChildViewController *child = [[ChildViewController alloc] initWithNib:#"nibName" withBundle:nil];
child.parentViewController = self;
[self.navigationController pushViewController:child animated:YES];
[child release];
Then inside the child, you can talk about properties of the parent directly, as they get set by the user in the fields of the child, so...
self.parentViewController.dataField = #"My data for an NSString property of the parent view controller!";
Then when you pop the child back off, you'll find that you actually set that data into the .dataField property of the parent view controller.
(All code in this answer was typed right in here and is intended as an example only--I make no promises about spelling, syntax, etc. )
The UINavigationController gives you access to the navigation stack, check the documentation here. You can there find your parent view in the navigation history and then it should be easy to access its properties.
To find it, the UINavigationController has a method as described above:
viewControllers
The view controllers currently on the navigation stack.
#property(nonatomic, copy) NSArray *viewControllers
Discussion
The root view controller is at index 0 in the array, the back view controller is at index > n-2, and the top controller is at index n-1, where n is the number of items in the array.
In short: your parent view controller is at n-2.