I have an application that can be started from the web via a URI scheme, that I have registered in my app. Depending on the URI, I open different view controllers (by setting the rootViewController in AppDelegate). Each view controller asynchronously gets some initialization data from a server upon initialization.
My problem is: If my server responds slowly and I go out of the application and enter the application again (causing a new view controller to open), then the first view controller still exists in the background and receive the server response for the request that it sent. This can cause an alert to be shown by the first view controller, even though that view controller is no longer in focus.
I could fix this by setting a flag in the viewDidDisappear: method. But this kind of handling messes up the code, and it would be much nicer if I could somehow stop the view controller from the AppDelegate. Or at least ensure that it only displays an alert view if it is in the foreground. Is there any way to do one of these things?
I found a possible solution in another SO question
if (viewController.isViewLoaded && viewController.view.window) {
// present alert view
}
This avoids the flags, but it still needs to be spread out where alert views are presented.
Related
I am in the process of building a reasonably complex iPad app that will run on iOS7+. The app has a login screen that must be accessed once every user session, this is the starting view controller in my storyboard. The user can log out from any other screen in the app (there are about 60 other screens) by touching a button that is always available in every other ViewController. When the user logs out, a custom transition animation should be used.
It seems I can achieve this in one of two ways, either with a segue from every screen in the app to the login page, which makes the storyboard impossible to read, or presentViewController.
I've implemented this with presentViewController, by looking up the view controller by id from the storyboard (which creates a new instance, which is a desired behavior) and then presenting it from the current view controller.
Not surprisingly, this does not dismiss the original login view controller and essentially creates a stack of view controllers, eventually I run out of memory as each time a user logs out, a new login view controller is created and retained.
Is there a way to clear this "stack" of view controllers?
Is there a different way to present a view controller, with an animation, that does not involve presentViewController or segues? I've considered view controller containment, but that doesn't seem quite right when used with storyboards.
Have you considered replacing the root view controller? If the app delegate observes a logout notification and replaces the root view controller with the initial content of the storyboard that should get the app back to the initial screen.
I've seen that presented as a solution for login/logout issues elsewhere on the web, but I'm not sure if there's a transition you can animate there.
I think your approach is wrong. Correct me else wise.
Login mechanisms are supposed to be singleton instances. Hence you should define your view controllers, models, views all as singleton instances. Please look at my following code for example.
static id objectInstance;
+ (id) sharedInstanceID {
if (!objectInstance) {
objectInstance = [[YourClass alloc] init];
}
return objectInstance;
}
Let me know if this helps you.
I am working on an IPhone app that, through viewDidLoad, makes a connection and pulls data into a table. I would like the controller, or at least the table, to reload every time the controller is displayed, even if someone just switched from one tab to another or closed and re-opened the app on this controller. Is there a way to do this? I can't seem to find anything, it's also kind of a hard thing to search for. I have found discussions of when to put code in viewDidLoad but not another method that is run every them the controller displays.
Thanks,
Cheryl
You want to use
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//code to reload table viewdata
}
Each time the view appears this method is called. I use this a lot in my app.
There's no way to do it by injecting code in only one place.
You can think of a workaround, like for example sending custom "reload request" notification from required parts of code:
viewDidAppear: in your view controller
applicationDidBecomeActive: (if table is presented)
tabBarController:didSelectViewController: (if switched onto controller with the table)
tabBar:didSelectItem: (same as above)
etc.
In you view controller simply observe this notification and reload data when required.
Although, whats more important: do you really need to reload data under such harsh requirements? In most cases data reload happens when
view controller's viewDidLoad: is called
it is manually initiated (button, for example)
long time passed since the last update was received
Otherwise it's just an overkill.
Okay, so I'm building an universal iOS app with an initial login view (view controller named LoginVC), just a plain simple UIViewController. If the login is successful the app segues to an navigation controller (MainNavigationVC). I created this segue through the storyboard gui of XCode, so no programmatic creation of the nav controller is done. The nav controller is presented modally in fullscreen, so the rest of the app is run atop the login view, with this nav controller as the centerpiece of everything.
The navigation controller contains a view (with a view controller named UserStartPageVC), and in its navigation bar is a logout button. This button sends an target action to UserStartPageVC, with the goal of dismissing the nav controller thus bringing the user back to the login view.
So far everything works fine. I can login and use the app as intended. But! When I log out and then re-login XCode tells me this:
Warning! Attempt to present <MainNavigationVC: 0x753110> on
<LoginVC: 0x756fcf0> while a presentation is in progress!
I suppose this means that the login view is trying to modally display a MainNavigationVC navigation controller, but another one is already displayed, right? But how? Can a view be presented without showing?
And how can I get rid of the old nav controller when logging out? I've tried several ways of dismissing the modal view, for instance:
from within UserStartpageVC running
[x dismissViewControllerAnimated:YES completion:NULL]
[x dismissModalViewControllerAnimated:YES]
where x is either self, self.parentViewController or self.presentingViewController.
setting the LoginVC as a property in UserStartpageVC and running
[self.loginVC dismissViewControllerAnimated:YES completion:NULL]
and so on.
All of the tested calls actually brings me back to the login screen, so it's kind of working.
Any ideas? Relevant code samples can be provided if necessary, I just couldn't figure out which pieces that were of interest. The seguing to the navigation controller has no code (except for a performSegueWithIdentifier:sender:), and the code for dismissing it is the part I cannot seem to get straight.
As a sidenote. So far this isn't a REAL problem; the app runs, and it IS possible to logout and re-login without any other side-effects than an error message in XCode. But I suppose this will be a memory leak if users logout and login multiple times, and I'm not in the mood of an unnecessary rejection from Apple.
I discovered another way to get the exact same error message. Lucky me!
If you created a segue at one point and had it tied to a button (click button -> new view) and then later give that segue a name and invoke it directly using
[self performSegueWithIdentifier:#"identifierName" sender:self];
then you can get this error because you can effectively trigger the segue twice. I thought making the button invoke an IBAction would turn off the segue I had set up in the first place, but apparently not. Hitting the button triggered the segue twice, but after I deleted the segue and re-created it as a manual segue on the view with the same identifier then I was able to invoke it via the above code and not get the warning message.
Hoopla! My bad.
Seemed I had set up the notification observing from the login API call in a stupid way. For every time the user triggered a login (or re-login), it added itself as an observer for the notification; the result was that it performed one more segue for every time a login was done.
And doing multiple segues at the same time, is... well, obviously bad.
I have a simple iPad application with 5 views. On the first view, the user is asked to make some selections and set some options. From this information, the other 4 views are programatically changed after an NSNotification message is sent to them. (i.e controls are added, updated).
My problem is that when the application is first loaded, the user sees View1, but View2, View3, View4 and View5 have never been opened yet, so any changes I make programatically to those views are not done and when the user navigates to them (via the tab bar) for the first time, no changes are shown.
[EDIT: I should point out that the code for making the changes to each view is contained within the ViewController itself, and is executed when the view observes the incoming NSNotification. When the view is not loaded, it understandably never received the incoming NSNotification.]
Only after the user looks at any of those screens at least once and then goes back to View1 and makes changes, are the other Views updated properly.
I thought I could get around this issue by actively loading Views 2,3,4 and 5 into memory on application start, so that they are ready to begin receiving notifications right away.
Is there an easy way to do this in iOS 5?
Why do the view changes straight away?
I would store an indicator of the changes needed when the users answers the questions on the first view and then apply the changes on -viewDidLoad of each view that needs to be changed.
Instead of trying to load the views into memory, I'd suggest you initialize these views with the options that the user set on the first view. What I usually do in such situations, when I have a global parameters that are used in many places, I create a utility class to keep the data, make it a singleton, then access the shared instance in the viewDidLoad in the views that use the data during initialization.
I have a tab-bar and navigation controller application (like Youtube app or Contacts app).
Where is the correct place to have the code for loading some data from the web? These data are necessary for all the tabs of the Tab Controller and the app can't display anything before all data are downloaded and parsed from the app (except a loading indicator view of course).
Up to now I put it in the AppDelegate but it somehow doesn't feel right..
What's the correct way to do it?
Thanks!
For a simple application, doing this in the application delegate is okay. I would probably do it in my main view controller, however. I would also probably put some defaults on those tabs so that the user sees something there, even if there is no internet connection (although this may vary between apps, and may not be appropriate in your case).
The most important thing to remember is that wherever you start loading this data (whether it's in the app delegate or your controller's viewDidLoad method), you should kick-off whatever downloads you need and establish whatever notification system is appropriate and return as quickly as possible. I.e., don't block in either of these delegate methods.
In general, though, since it sounds like this data is related to the display you are creating, it's probably appropriate to contain it's loading inside the view controller itself.