I've an iPhone application:
When you open the app you see the "LoginView". If you login into application you see a TabBarController. In the third and last tab there is "Logout" button. If you click you see the "LoginView" again. My problem is that if you login again you see the "old" tabbar and the selected tab is the third and not the one, and there is a "Logout" button. Also, if a user login with a different user, see the old data of the previous user (very dangerous).
Here's the code:
- Delegate.h:
UITabBarController *tabBarController;
LoginViewController *loginView;
- Delegate.m (didFinishLaunchingWithOptions):
[self.window makeKeyAndVisible];
loginView = [[LoginViewController alloc] init];
if (YES) { /* if the user is not already logged */
[self.window addSubview:loginView.view];
}
Delegate.m (methods):
- (void)loginComplete {
[loginView dismissModalViewControllerAnimated:YES];
[window addSubview:tabBarController.view];
}
- (void)logoutComplete {
[[tabBarController view] removeFromSuperview];
[tabBarController release];
[window addSubview:loginView.view];
}
And here's the two methods in two different viewcontrollers:
- (IBAction)login:(id)sender {
TabNavisAppDelegate *delegate =
(TabNavisAppDelegate *) [[UIApplication sharedApplication] delegate];
[delegate loginComplete];
}
(the logout method is the same)
Guys, how can I solve this painful problem?
So, here's a list of application that do what I want: "Foursquare", "Brightkite" and others.
Each one have a login screen, a tabbar view and a logout button.
Thanks # everyone.
For login-logout-login situations where all kinds of things need to reset themselves at the logout or the next login, I like to create a notification, something like "NewUserReset." Everything that needs to reset itself to an original state listens for the notification and runs a method that does whatever kind of resetting it needs. The tabbar would change the button title to logout, temporary data structures nil/zero/release themselves, etc.
It's nicely decouples the logout from all of the things that have to be done so you're not trying to manipulate view controllers and data storage and view appearances from the the controller that received the logout tap.
Sending a notification is easy. When the user taps the Logout button you'll send out a notification like this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"JMUserLogout"
object:nil];
You don't have to call it JMUserLogout, you just need a string that you'll recognize and something -- I used your initials -- to help ensure you don't accidentally send a notification that has the same name as a notification something you're unaware of is listening for.
When that notification goes out, any object that has registered with the defaultCenter to listen for #"JMUserLogout" will perform any actions you choose. Here's how your object registers (this should be located in some place like ViewWillLoad or the initialization method of the object):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(resetForNewUser:)
name:#"JMUserLogout"
object:nil];
The selector there, resetForNewUser:, is just the name of a method you want to run when the notification goes out. That method looks like this:
- (void)resetForNewUser:(NSNotification *)notif {
// DO SOMETHING HERE
}
Where it says // DO SOMETHING HERE you'll add the code specific to your app. For example, you can add the tab bar as an observer of the JMUserLogout notification. In its resetForNewUser: method you'd change the name of the logout button to Login.
In a ViewController or View or data store that holds old data from the previous user the resetForNewUser method would delete all of that data and set things back to the way they should be fore a new user. For example, if the previous user entered data into a UITextField you would delete the text, yourTextFieldName.text = #"";
Lastly, it's important that you also remove your object as an observer before it's deallocated. In your Dealloc method of each object that registered to receive the notification you add this:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Hopefully that makes sense. The Apple documentation for NSNotificationCenter explains more and they provide several sample apps that use notifications.
Seems like tabBarController is not getting released. [ retain count should be 1 before releasing] tabBarController might be retain somewhere. check the retain count of it.
If you want to reset the old data from the previous user after you log out.. all you have to do is reset the UITabBarController's viewControllers property.
So if you are subclassing UITabBarController the following code should restore your app to its original state.
self.viewControllers = #[self.viewControllerOne, self.viewControllerTwo, self.viewControllerThree];
From the documentation:
If you change the value of this property at runtime, the tab bar controller removes all of the old view controllers before installing the new ones. The tab bar items for the new view controllers are displayed immediately and are not animated into position.
The tabBarController object may have been retained somewhere. Try to remove that.
And use the following code for Login, Logout methods
- (void)loginComplete {
// initialize the tabBarController here. like the following
if(tabBarController == nil){
tabBarController = [[UITabBarController alloc] init];
}
[loginView dismissModalViewControllerAnimated:YES];
[window addSubview:tabBarController.view];
}
- (void)logoutComplete {
[[tabBarController view] removeFromSuperview];
[tabBarController release];
tabBarController = nil;
[window addSubview:loginView.view];
}
So that your problem will be solved.
Related
I'm trying to build an iPhone app that allows users to register from their phone itself. At the first page (login form) I have a "register now" button that displays a Registration Form to do just that.
So the workflow now is:
User stars app, load form A
App detects that user is new, UIAlert to say that User needs to register
User has to click 'Register Now' to register (form B)
I'm wondering how I'd improve the app by doing something like this:
User starts app, load form A
App detects that user is new, UIAlert to say that User needs to register
The Registration Form (form B) automagically appears.
I tried implementing some logic in Form A's viewDidLoad like:
//initWithNib:#"RegisterForm" doesn't make a diff
RegisterForm *formB = [RegisterForm new];
formB.navTitle = [NSString stringWithFormat:#"Register New"];
[self presentModalViewController:formB animated:YES];
[formB release];
But that doesn't work, so, what I'm doing wrong?
-(void)viewDidLoad {
If the user hasn't registered
{ RegisterView *view = [[RegisterView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:view animated:YES];
[view release];
} else {
}
}
Or, if you're working with multiple views within one XIB file, define the view in the .h file, link it in the XIB file, and then in viewDidLoad, use the same if statement, but use this code instead:
[self.view bringSubviewToFront:registerView];
In my iPhone app, I have the unique identifier for eah project. I want that when the user logs in the unique identifier be accessible on every page.
I am using NSUserDefaults to do that. But the problem is that Login is not the first page. So if I access some page which requires unique id, then it shows "unknownkey".
How can I work around this problem?
Also, I can't add the login screen as first screen as the user can access some portions without login.
try something like
if (![[NSUserDefaults standardUserDefaults] objectForKey: #"uniqueid"]) {
//...user is not logged in..
} else {
//...user is logged in...
}
I guess that this is a design problem rather than a programming problem?
You could probably either move your login screen to the startup screen or leave the tab bar as it is until the user logs in, or even re-design the whole thing differently.
If the login is necessary for everything (e.g. your app is a dropbox client), present the login viewController modally at application start.
And don't dismiss it until you have a successful login.
In your RootViewController's viewDidLoad method, you need to check the saved NSUserDefault via an IF statement. Easy.
-(void)viewDidLoad {
if(![[NSUserDefaults standardDefaults] objectForKey:#"uniqueid"]) {
// here you'll want to present a view modally inside a new navigation controller.
LoginViewController *lvc = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
UINavigationController *controller = [[UINavigationController alloc] initWithRootViewController:lvc];
[self.navigationController presentModalViewController:controller animated:YES];
} else {
// this is where you'd set up the client if the user is already logged in.
}
}
Ok, so this is actually solved - but I don't understand why what I did worked.
My issue was that sending a notification once would cause one event to fire multiple times. I ended up with several unwanted views on the stack. in a nut shell:
the user presses a button in the toolbar, a notification is sent from the delegate
mapItem = [[UIBarButtonItem alloc] initWithImage:mapImage style: UIBarButtonItemStylePlain target:self action:#selector(mapButtonPressed:)];
-(void)mapButtonPressed:(id)sender{
NSLog(#"Map Button Pressed");
[[NSNotificationCenter defaultCenter] postNotificationName:#"mapButtonPressed" object:nil ] ;
}
this fires a function in the current view to push a map view onto the stack.
-(void)openListMap:(NSNotification *)aNotification {
mapViewController = [[MapViewController alloc] initWithNibName:#"MapViewController" bundle:nil];
NSLog(#"Map Created");
mapViewController.searchLocation = searchLocation;
if(givenLocationType == #"input"){
mapViewController.inputLocationText = inputLocationText;
}
mapViewController.givenLocationType = givenLocationType;
CultureNOWAppDelegate *delegate =
[[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:mapViewController
animated:YES];
}
This works now, I changed the last line from:
CultureNOWAppDelegate *delegate =
[[UIApplication sharedApplication] delegate];
[delegate.navigationController pushViewController:mapViewController
animated:YES];
to:
[self.navigationController pushViewController:mapViewController animated:YES];
The result is that, although the openListMap function still fires multiple times (you can see in the console, the log output shows "Map Created" for each time the view has appeared since the app started) it only pushed the latest mapView onto the stack.
But why? Why was it firing multiple times in the first place, and why has it stopped by exchanging two piece of code which, for all intents and purposes, are the same?
Thanks for any thoughts.
The fact that it is firing 2 times warns me that your "fix" is not really a fix - it is a behavior of Apple's API that happens to do what you want.
I had a similar issue with an app where a notification was inexplicably being fired twice. What I realized later was that the notification is only called once - but that inside NSNotificationCenter, there is nothing to stop you from registering TWO observers for the exact same event for the exact same selector callback.
This happened to us because we added observers in viewDidLoad, but never removed the observers in viewDidUnload. Then, when the user's phone had low memory (which, thanks Apple, happens a lot in iOS4+), the views get flushed, and you end up with 2 observers when viewDidLoad gets called a second time.
That may not be your exact problem - but I would like to hear about where you are registering for observer notifications.
i am trying and trying.... but no result yet.
i have a view with a navigationcontroller and -bar. in addition there is a tableview.
if you click on a row, the iphone email function is getting startet by using the email example from apple (MailComposerViewController.h and .m).
it opens and i am able to send or cancel the mail. after that the "dismissModalViewControllerAnimated" methode is dismissing the emailing view. everything looks right, i am in the previous view now, BUT:
when i want to use buttons from the navigationitem or if i want to add rows to the tableview, the app is crashing without any error message.
do i have to set something back or to "remove" something which is still there because of the mailing view?
it's really strange and i am searching now for hours....... :-(
thank you very much in advance!
hopefully someone has an idea.
EDIT:
ok, here i have the code now:
in a tableview which is included in a navcontroller i have following lines to open first a Subclass of UIViewController:
- (IBAction)Action:(id)sender
{
DetailViewController *editViewController = [[DetailViewController alloc] initWithNibName:#"TripDetailViewController" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:editViewController animated:YES];
editViewController.navigationItem.prompt = #"Details";
[editViewController release];
}
in the DetailViewController following action is processed when the user clicks on the button:
- (IBAction)mailTrip:(id)sender {
MailComposerViewController *mailComposer = [[MailComposerViewController alloc] init];
[[[self view] window] addSubview:[mailComposer view]];
[mailComposer showPicker:sender];
}
when i press the related button, the MailComposerViewController pushes up correctly. all functions of the MailComposerViewController are working correct.
but when i send or cancel the mail and the MailComposerViewController disappears, my previous view doesn't work anymore.
in the MailComposerViewController-Example from apple the MailComposerViewController just disappear and the previous view is working fine again.... :-(
ok, i found the stupid simple problem.
the MailComposer-View was after dismissing it still over the previous view.
i did set the propert hidden after dismissing and now it works...
[self dismissModalViewControllerAnimated:YES];
[[self view] setHidden:YES];
but i am really not sure if this is the right way to do it...
I'm creating an iphone app with few elements inside the controller (e.g tab bar, uiview, uitoolbar, etc..). Everything works fine until I encountered this problem. While my application is launched, I received a call and it shows the "Call Status Bar" which ruined the ui. Some elements are pushed down because the "Call Status Bar" is taking space at the top.
Anybody here have an idea on how to fix this issue? I'm new to iPhone app development.
Your reply is greatly appreciated...
Best Regards,
dianz's solutio works just fine but is a bit redundant if you are only interested in knowing about the notification inside of a specific view controller.
After the delegate method application:didChangeStatusBarFrame: is called in the Application Delegate UIApplicationDidChangeStatusBarFrameNotification is posted through [NSNotificationCenter defaultCenter].
Instead of using the delegate method application:didChangeStatusBarFrame: to simply repost a custom notification you can add an observer to UIApplicationDidChangeStatusBarFrameNotification directly from your view controller.
In MyCustomViewController you would add something similar to this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
UIApplicationDidChangeStatusBarFrameNotification
object:nil];
Now you no longer need to define the application:didChangeStatusBarFrame: delegate method in appDelegate (unless you plan to do something in the appDelegate when the status bar changes size).
As with dianz's example you need to remove the observer in dealloc
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
You should put this function on appDelegate, this will trigger when the status bar change
- (void)application:(UIApplication *)application didChangeStatusBarFrame (CGRect)oldStatusBarFrame
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:#"trigger" forKey:#"frame"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"trigger" object:self userInfo:dict];
}
This Code will send Notification with the name "trigger"
Place a code to your view Controller (e.g: viewDidLoad, etc..) this listen if there are notification send with a name "trigger"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(dataReceived:)
name:#"trigger"
object:nil];
And create a function dataReceived:
- (void)dataReceivedNotification:(NSNotification *)notification {
NSDictionary *data = [notification userInfo];
// do something with data
}
do something on this part of the code, maybe you change the frame of your tab bar, uiview frame, toolbar frame
And in dealloc, put this code to remove the observer
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Basically what you normally do is try to set up the autoresize flags of all your ui elements in interface builder so that when the main view is "squashed" by the call status bar everything will still look reasonable. It's a little hard to explain how to do all of this in one message, but I recommend creating a view in IB, placing some subviews in it, then resizing the main view while playing with the autoresize flags to get a feel for how the flags work. The autoresize flags are in Command-3 (size inspector).
You can also set wantsFullScreenLayout in the main view controller to YES to cause the view to take up the whole screen, including the area under the status bar, but then you'll have to make sure not to place anything under the status bar and the call status bar will overlap anything too close to it, of course.
for me, whenever the status bar is enlarged, the -(void)viewWillLayoutSubviews is always called. This is perfect for me because all my subview setFrame code is in this function.