So I have two views A and B. A is a profile view, B is a login view. A loads B in the ViewDidLoad Method using
LoginViewController *lvc = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:[NSBundle mainBundle]]; //make new instance of LoginViewController
[self presentModalViewController:lvc animated:NO]; //present the LoginViewController
[lvc release];
in the login View, if the login is successful, the view is removed
[self dismissModalViewControllerAnimated:YES];
On the login view, It downloads some data which I want to display on the profile view. How would I go about sending the data to the profile view and displaying it in the xib. I believe the profile view is already displayed but is just hidden.
This is a basic "communicate between two classes" question. There are many ways to do this, but here are three. I only wrote sample code for delegation (because I think that's probably the best choice in your situation), but let me know if you want examples of notifications or KVO.
Delegation Implement a delegation or callback method in Class A. Delegation is best for small class hierarchies. If Class A is the only class that will load B and A is the only class who cares what happens in B, then delegation is the easiest way to move data around. It's simple to implement, it's simple to understand and there's a clear relationship between the classes.
// Class A
- (void)displayLoginViewController {
LoginViewController *lvc = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:[NSBundle mainBundle]];
lvc.delegate = self;
[self presentModalViewController:lvc animated:NO]; //present the LoginViewController
[lvc release];
}
- (void)loginViewControllerWasSuccessfull:(LoginViewController *)loginViewController {
// Do whatever you need to do here
[self dismissModalViewControllerAnimated:YES];
}
In the login view controller do something like this in the header:
#property (assign) NSObject delegate; // declared assign so you don't have circular references
… and this in the implementation:
- (void)didLogin {
[self.delegate loginViewControllerWasSuccessfull:self];
}
Notification Class A will register to listen for login notifications. Class B will post login notifications. Notifications are best if the classes that care about login are distributed. i.e. there are many classes that care about a login event and they may not necessarily have a direct relationship with the class that is performing the login.
Key Value Observing KVO is best if you don't particularly care about the login event, you care about the changes to the data. You will have some class that manages your data, probably an NSManagedObject if you are using Core Data. Class A will observe changes to whatever property it's interested in. Your LoginViewController will update that data class when it is finished downloading data. Class A will be notified that the data has changed.
Whatever solution you decide to use, the choice ultimately comes down to asking, "What does Class A care about?". Does Class A need to know that Class B successfully logged in? Use delegation. Does Class A need to know that somewhere, some class logged in? Use notifications. Does Class A not care about logins, it only needs to know if data has changed? Use KVO.
You Load view A after downloading the data instead of ViewDidLoad.
when u click on the login button then download data and display it.
if your viewWillAppear is not calling then create nsnotification center object and post it when you want to call your view willAppear method.and then remove this notification.
you can store the downloaded data at delegate file in login view. And in viewWillAppear method of profile view use data from the delegate....
for that you have to create variable and set its property in .h and .m file .than you can set this variable value in login screen and it will synthesize to profile screen.
Another way
you have to create variable in appDalegate . appDalegate value set in login screen and use this value in profile screen
If I understand correctly, you are trying to do the equivalent of Android's Intents. Therefore I advise using iOS's NSNotificationCenter and send NSNotifications with associated data.
Related
In MyViewController, I ask the user to select a person from address book: (on a button click)
peoplePicker = [[ABPeoplePickerNavigationController alloc] init];
peoplePicker.peoplePickerDelegate = self;
[self presentViewController:peoplePicker animated:YES completion:NULL];
If user selects a person with more than one phone number, I present a new UITableViewController which allows the user to select one of the phone numbers:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
// ...
next = [[ChoosePersonPhoneViewController alloc] initWithStyle:UITableViewStyleGrouped personPhoneInfoArray:personPhoneInfos];
[peoplePicker pushViewController:next animated:YES];
// ...
}
When the user selects a phone number, I would like to bring that phone number back to original ViewController and close both ABPeoplePickerNavigationController and ChoosePersonPhoneViewController.
Two questions:
How do I close both view controllers from within ChoosePersonPhoneViewController?
How do I access MyViewController from within ChoosePersonPhoneViewController?
My guess would be that:
ABPeoplePickerNavigationController *peoplePicker = (ABPeoplePickerNavigationController *)self.navigationController;
MyViewController *nsvc = (MyViewController *)peoplePicker.presentingViewController;
I have tried many combinations using parentViewController and presentingViewController and popViewController / dismissViewController, but can't really figure it out - I keep getting exceptions which show my lack of understanding of the view controller hierarchy. I would appreciate some pointers or at least RTFM links.
Once you have your second question answered, your first question becomes easy. In ChoosePersonPhoneViewController, have a property of the same class as myViewController named parent, and when you allocate ChoosePersonPhoneViewController from myViewController, set it to self, much like you set the delegate to self. Once parent is set, you can call methods in parent from the child object.
Handling changes, actions, or user interaction in other views is the essential use case of delegates. The best practice is to have the first view controller be the delegate of NewSettingsViewController, and then as the user chooses one of the phone numbers, it calls certain methods on its delegate to notify it of the final choice. Typically the delegate can determine if it is satisfied with the results it gets and can then dismiss any controllers that are no longer needed.
Other options for keeping values/state in sync between views are:
Key-Value Observation - best used for data
NSNotifications sent through NSNotificationCenter - best used for actions
You should try on your ChoosePersonPhoneViewController to access the presentingViewController which apple doc states that:
this property holds the view controller presenting the nearest
ancestor
So you can try something [ChoosePersonPhoneViewController.presentingViewController dismissModalViewControllerAnimated:YES] (note this method is deprecated in iOS 6).
So, I understand the basic example of delegates. What I have is this:
WebService (class to handle grabbing web data)
HomeViewController (home screen)
ProgressViewController (shows the progress of a long download modally for long downloads)
OtherViewController (another view controller that might make a quick network request)
Scenario 1: So from the home screen, they can make a download where we would then ask the web service to get the data, and show the progress if it's a long download.
Scenario 2: OtherViewController might need some simple information from the internet. It asks the web service for that data, and updates that view.
Currently, everything is handled with NSNotifications.
Scenario 1 with NSNotification: home screen presents modal view controller, adds the ProgressViewController as a listener to the webservice, ProgressViewController updates its screen when needed.
Scenario 2 with NSNotification: other view controller gets registered as an observer of the web service in viewDidLoad, gets the callbacks when needed from the web service.
I was wondering if and how I could set this up through delegation. I thought it might be better to have a WebServiceDelegate that could implement methods like:
- (void)webService:(WebService *)webService didUpdateProgress:(double)progress;
The problem I see with this is, if my web service starts a request to download some large amount of data, currently, the home screen view controller will do:
ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
to present the view controller, and then it listens for the progress updates.
I don't see how I would do it through delegation since I don't know where I would set the delegate property. In the WebService, I need to do something like:
self.WebServiceDelegate = progressViewController;
However, the progressViewController doesn't get created in the web service. It gets created on the homeViewController. The only thing I have come up with so far is do something like:
ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
pvc.progressViewControllerDelegate = [WebServiceManager sharedInstance];
self.webServiceManagerProgressDelegate = pvc;
NSDictionary *progressViewDict = #{ #"ProgressViewController" : pvc };
[[NSNotificationCenter defaultCenter] postNotificationName:WebServiceShowProgressViewNotification object:self userInfo:progressViewDict];
Where the web service knows that it's supposed to show this view controller, posts the notification for that, and then whoever (in my case the home view) was listening, can show the progressViewController, and then the progressViewController can show the progress and respond to web service delegate methods. It seems kind of roundabout and I didn't know if there was a better way to do this, or just stick with notifications. Thanks!
If I understand your situation correctly, homeViewController has a reference to the web service, and then homeViewController creates an instance of ProgressViewController. Then ProgressViewController needs to get updates from that web service using delegation. You could try something like this as part of HomeViewController:
ProgressViewController *pvc = ProgressViewController *pvc = [[ProgressViewController alloc] initWithNibName:#"ProgressViewController" bundle:nil];
self.webService.delegate = pvc;
Of course, this requires homeViewController to have a reference to WebService (I called this webService).
Where the web service knows that it's supposed to show this view controller
It really shouldn't know about any of that. It just needs to do its thing (download, upload, whatever), and if there's something to report, it does so by sending its delegate a message. That delegate (whoever that may be), will then update views accordingly.
EDIT: I just realized that WebService is a singleton. I believe the preferred approach for singletons is actually using notifications. Since all view controllers can access a singleton, delegation isn't always an option as all these view controllers may need an update on what's going on, while the object can only have one delegate. If only one or two view controllers use the WebService singleton, you should probably not make it a singleton.
I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I have a tab bar controller with 4 tabs. Each tab has its own view controller and a UIWebView.
Let's say I have a button (button1) in vc1 and an instance method onClick1 as well. In vc2 I have a method named reload. My question is, how do I access the specific instance method, onClick1 in vc2, from vc1?
For further detail, I'm actually trying to code a simple shopping utility for the iPhone. When a user adds an item to the cart from the browse view, I want to be able to automatically reload the cart view.
Below are some examples of what I mean. This problem has been more difficult than I thought. I'm not sure if I have redesign my application or what. Perhaps have both vc1 and vc2 belong to a subclass of vcmain and have reference to each of them there? However, if I do that, then how do I refer them to their corresponding .xib? Thanks guys!
#implementation viewController 1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls vc2 reload
[vc2 reload];
}
#end
#implementation viewController 2
//Reload View
-(void)reload {
[webView reload];
}
#end
You've basically got three approaches, each one of which has plusses and minuses. I'm going to give you a high-level overview and let you go to Apple's quite comprehensive documentation for details. Hopefully I can give you the right terms to google for more specific help.
The approach that #dredful quite ably details is to have a handle to the "other" view controller(s) and call methods on them directly. That works fine, but it can be confusing and cumbersome handing pointers to all your controllers around, and traversing the view hierarchy to get get at the controller you want can be very tricky indeed.
The second approach is Key-Value Observing. You can register one view controller to "watch" a particular key (named property) of another view controller, and fire a particular selector when various things happen with it. This is kind of magical and nice, although at some point you have to have pointers to both controllers at the same time, which doesn't entirely relieve the downside of the "call it directly" method above. It is also a sort of unfortunate coupling of view control and data, kind of breaks MVC.
The third approach is using NSNotificationCenter. A class can post a notification, and any other object that registers itself to listen for that sort of notification can be triggered when that happens. It's nice because you might have LOTS of different objects adding items to the cart, and they can just shoot the notification center a note (even passing it an object or arbitrary data, if it wants), and the cart view can consume those notifications, catch the passed objects, and do its thing, not caring in particular who's talking to it. It keeps separate pieces of your application nicely decoupled. The downside is, it's got a bit of overhead to it, and whatever selector the notification-consuming class performs happens synchronously, so you can't hide network activity or some other long process there.
I'm thinking you should already have something like a base UIViewController (let's call it MyTabBars) that has a UITabBarController *tabBarController containing all your Tab Bar View Controllers. If that sounds familiar, you will want a method in MyTabBars called -(void)reloadCart. reloadCart will walk the array of tabBarController.viewControllers. On each viewController you can perform a respondsToSelector:#selector(reload) and if the specific viewController qualifies then it calls that selector method.
In order to do this you probably want all your vc1, vc2, ... files to have an id delegate defined and synthesized. When MyTabBars creates the different tab bars it sets the vc1 and vc2 delegate to self.
#implementation MyTabBars
//Reload Cart View
-(void)reloadCart {
for (UIViewController *thisUIViewController in tabBarController.viewControllers){
if ([thisUIViewController respondsToSelector:#selector(reload)]) {
[thisUIViewController reload];
}
}
}
#end
Assuming you know how to pass a delegate of MyTabBars into your vc1 and vc2, then you can now have the following code in vc1:
#implementation viewController1
//Reloads vc2
-(IBAction) onClick1: (id) sender {
//Calls MyTabBars reloadCart which will look for all tab bar view controllers
//that have the 'reload' method
[delegate reloadCart];
}
#end
This solution idea will cause MyTabBars to trigger any reload method found in any of our tab bar view controllers. Therefore be careful with the naming of such a method in your vc1, vc2, etc files. This solution will trigger a unique vc method or multiple vcs with the same method depending on your naming convention.
Hope this helps.
Before iOS4, my app's initial view controller would check a passcode on/off settings variable in viewWillAppear and if set on, present a modal passcode screen that would stay there until the correct passcode was entered or the Home button was pressed.
With iOS4, if my app has been in the background, I would like the user to feel comfortable that the data contained within the app is not easily accessible if they were to hand their phone to someone to use.
Since the app could return to any screen (the screen that the app was last on), I figured I would use the UIApplicationWillEnterForegroundNotification all over the place with local selectors (duplicate enterPasscode methods), each having the correct view controller to push based on the local screen, but there has to be a better way.
I may be having a slight conceptual misunderstanding here. Can someone suggest another approach or nudge me along to the next step. Can I have this as a shared method but still know the correct local view controller to push?
Thanks
EDIT/UPDATE:
Short version: It works, but may there still may be a better way (any help appreciated)...
I created a standard singleton with a view controller.
PasscodeView.h containing:
UIViewController *viewController;
#property (nonatomic, retain) UIViewController *viewController;
PasscodeView.m containing:
#synthesize viewController;
I put this in the AppDelegate:
-(void)applicationWillEnterForeground:(UIApplication*)application {
PasscodeView *passcodeView = [PasscodeView sharedDataManager];
UIViewController *currentController = [passcodeView viewController];
NSLog(#"%#", currentController); //testing
EnterPasscode *passcodeInput = [[EnterPasscode alloc] initWithNibName:#"Passcode" bundle:nil];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
and the following in all my viewDidLoad, updating the current view controller as I went into each screen (only 2 lines but still seems that there's a better way):
PasscodeView *passcodeView = [PasscodeView sharedSingleton];
passcodeView.viewController = [[self navigationController] visibleViewController];
I wish there were a way to have gotten the current view controller from applicationWillEnterForeground but I couldn't find it - any help here still appreciated.
For consistency, I changed a line and added a line to get a nav bar to match the rest of the app and to include a title.
UINavigationController *passcodeNavigationController = [[UINavigationController alloc] initWithRootViewController:passcodeInput];
[currentController presentModalViewController: passcodeNavigationController animated:NO];
You can centralize this behavior in your app delegate by implementing
-(void)applicationWillEnterForeground:(UIApplication*)application;
You might implement a singleton that stores the currently appropriate modal view controller, updating it in viewWillAppear in each of your view controllers.
Edit: I was assuming that you already had a series of view controllers that you wanted to show. I doubt you actually need it. If you have one called, say PasscodeInputController, then your applicationWillEnterForeground would look something like:
-(void)applicationWillEnterForeground:(UIApplication*)application {
UIViewController *currentController = [window rootViewController];
PasscodeInputController *passcodeInput = [[PasscodeInputController alloc] init];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
I hope that addresses your question more directly.
Your application's behaviour when reentering the foreground should be as similar as possible to when it launches for the first time. With this in mind, you could combine your applicationDidFinishLaunching: with applicationWillEnterForeground: but considering some views might already be loaded. The code is something like this:
id myState = (isReentering) ? [self validateState] : [self loadStateFromSave];
NSArray * keyForObjectToLoad = [self objectsToLoadForState:myState];
for(NSString * key in keyForObjectToLoad)
if(![self objectForKey:key]) [self loadObjectForKey:key];
Depending on your app's detail it might require initial work, but it has some benefits:
It will ensure launching and relaunching are similar, hence the user experience is not "random".
You can free many unwanted memory easier when in the background.
It's easier to manage your application's state from a central place.