I have a method that reads some data from a database and returns it in a closure, for example:
DatabaseManager.readUser(withUserID: "Bob") { (user, error)
MySingleton.shared.user = user
}
I have a home VC which is my landing page, and a "user" VC that a user can navigate to. My user VC needs to display the data read from the database, in this case MySingleton.shared.user.
My problem:
I am currently trying to load the user's data on a background thread in my home VC, so by the time the user navigates to the user VC, the data can be displayed. However, if the user navigates to the user VC too fast, before the data was read, my app crashes because MySingleton.shared.user is nil, as expected.
My other option would be to load the data in viewDidLoad() in my user VC and display it after it was set in the closure. But this might cause there to be a kind of blank screen between the time the VC loads, and the time the data was read.
What is the best option here for a seamless user experience? Is there a way I can try to preload it in my home VC, and if its still not set by the time I navigate, wait until it is before displaying the user data?
If you want to start data loading when the screen is opened you can proceed in the following way:
On button tap check that you data is loaded. If yes - open the next screen, if no - set a flag (for instance openScreenOnDataLoad = true) and display a progress indicator.
In your closure when the data load is finished check that flag and open the next screen if the flag is set.
If you are initiating the data load in some other place/class you can use notification to inform other parts of the program that the data was loaded.
My general advice in these type of cases:
When Moving to a new ViewController, any required dependancies should be injected at the time of initialization/segue, and everything else should be handled internally during the ViewController's life cycle.
This makes the requirements for showing the ViewController clear, minimizes state complexity, and increases modularity.
In your case that means either the HomeVC needs to load the date (preferably indicating loading somewhere in the UI) and send it to the UserVC when it's made, or the UserVC will need to load the data and handle any loading UI there.
Personally I would prefer the second option minimize dependencies, simplifying navigating from anywhere else.
As an aside, if you're sure you want to use a singleton like that it's generally a good idea to handle the singleton management (creation, destruction) in the Model, and (at most) only access it from your ViewControllers
Related
Here's the scenario:
-A UIViewController (A) is pushed onto the navigation stack
-On viewDidLoad an async GET is called using AFNetworking (a singleton AFHTTPClient shared throughout the application) to populate various user elements on the view (say a UILabel).
-The user presses the back button before the request returns
-Assume other active view controllers may be making requests so you can't cancel all open operations
So question #1 is, should you track the open requests made by UIViewController A and cancel the outstanding ones when the user leaves that view, or should you let them finish up and ignore them? Since AFNetworking uses blocks, the user elements being updated are retained inside the block and therefore won't cause a crash when the success/fail block is executed after the view has been popped. However the downside to ignoring them seems to be unnecessary network traffic.
Question #2 is, where would you execute the code to cancel the operations made by UIViewController A? viewDidDisappear doesn't seem right because the user may have gone forward (pushed a new view onto the stack) instead of back (popped the current view), in which case you don't want to cancel the open requests because the user may come back to the current view and it won't load again. However, I don't think dealloc or viewDidUnload will be called while the request is executing since the block will keep a retain on the user elements so I don't think it can go there.
Would appreciate thoughts on this. What do you think is best practice?
Generally speaking, you don't really need to cancel requests when a user leaves a view controller. In terms of memory management, a reference to block self will prevent any crashes caused by sending messages to deallocated instances, so no worries there.
As far as user experience, I would say that you shouldn't really worry about it until it's a problem (we developers have a knack for guessing completely wrong on what will be slow in our applications). If you are making large GET requests, though, and it's creating noticeable sluggishness, my suggestion would be to have the controller do HTTPClient -cancelAllHTTPOperationsWithMethod:path: in -viewDidUnload: (any other callback would be premature).
Maybe you could have a singleton which manages all the network stuff, and just set its delegate to the current vc (in viewDidLoad) so you get any incoming data, and send it a cancel message when the vc disappears (or else let a different vc become its delegate). Or the singleton could keep the data for access by any vc at some later stage. I tend not to put async code into my VCs for this reason.
In my app there is authentication required, so when you launch one of the tabs on tab bar, "class A" checks are there credentials saved if not, "class B" modal view controller with fields to login launches.
So my question is : in which method in class A (loadView, viewWillAppear or maybe in another one) should be implemented checking if there are credentials saved and other stuff described above.
And my additional second question is:
is pushing modalviewcontroller correct way to show login screen, or i should do that differently?
Thank you for reply guys.
OH ! One More Thinh
And one more thing. I've done implementing LoginView by adding delegate and presenting ModalVC (Harkonian the Piquant's method). But in my tab bar app i have got very confusing problem. I mean when user taps login button (assume that everything was correct and he's able to secured data) how PROPERLY switch to tab where is secured info. I mean previously selected tab.
i did it by adding in
-(IBAction) login {
//some code
self.tabBarController.selectedIndex =1;
And it seem to work good but is it correct ?
I have a very similar use case in my app -- it requires a passcode to authenticate. After a lot of testing and tweaking I found the following design to be the best approach:
Don't use class A to launch your credentials VC -- use the app delegate instead.
For security purposes, typically you'll want the credentials VC to show before the user can view the underlying view. It's much easier to handle this in the app delegate than in a VC. In addition, you need to consider what happens when your app is backgrounded -- a screen shot is taken of the current state of the app. If you are using viewController A to show the credentials view, when the app relaunches the user will be able to see whatever sensitive information was visible on app close until the app finishes launching and VC A presents the credentials VC.
Don't insert your credentials view into an existing ViewController -- use a new UIWindow instead.
You don't ever want any other view to be able to sit on top of your credentials view. Ever. Even views that would normally always be on top, like UIAlertView. The easiest way to achieve this is to have a special UIWindow just for your credentials view. Show this window and hide the primary app window whenever you need to display the credentials view.
How does this approach look in practice?
If you are at all interested in how well this design works, you can check out the passcode feature in Audiotorium Notes for iPad. I spent a lot of time with this design to make sure it was as secure as possible.
If you have any specific implementation quests feel free to ask and I'll try to answer them.
I'm doing an app that uses a TabBarController and each Tab uses its own navigation controller.
The app has dynamic content and I use viewDidDisappear viewDidAppear methods to create or destroy the objects that I need each time I enter or exit into the ViewController.
My problem is when I start to sail very fast and I don't give time to load the Threads that I use for uploading content such as XML peta app or destroy objects when I leave the ViewController.
How I could control the tabs of the navigationbar or tabbarviewcontroller for not respond until the viewcontroller has loaded all contents?
Excuse me if I'm not well expressed. Thanks!
No matter you use synchronous request or asynchronous request, just show an UIAlertView while loading the data. This will both serve as a notification to the user that something is being loaded, and the it will block the interactions with all the other views on the screen.
As others have suggested in comments, I believe that what you want to do is rearrange the order in which things are triggered. Perhaps something like this:
On viewWillAppear:, clear (or disable or whatever is appropriate) your objects that are no longer valid and begin the load-new-content thread. Perhaps display a UIActivityIndicator or similar.
On viewWillDisappear:, tell the load-new-content thread that it can stop, its results are no longer needed. If you put up an activity indicator, take it down.
At the end of the load-new-content thread, take down any activity indicator, update the UI with the new contents and activate.
I don't really see any way around this -- if the UI is not valid until the new content is loaded, then you have to wait for it.
Another solution might be to cache the contents from the previous fetch, and always display those on viewDidLoad. Then, at the end of your new-content-thread, cache the new contents, and update the UI.
I was wondering how you would save the state of a view after it runs an action that leaves for another view then comes back?
Please Help
Store state variables in NSUserDefaults or use NSArchive / sqlite database. Depends on what kind of state information you wish to store. Always assume your previous view will 'unload' if a new view is loaded (using presentModalView or the UINavigationController). This way you're always prepared for the worst. Under low memory conditions the view currently not visible gets its 'view' unloaded and thus you must recover your previous state yourself (you could even simply use class variables if the first view isn't destroyed).
I am having trouble finding some information on persistence in iPhone apps. I am creating a tab based - navigation based app and want the application to save the current location when the app quits. For example if the user is in tab 1, several tiers into the navigation controller and the app quits, I would like the app to load up in the same place the next time it loads.
If anyone can point me in the direction of a good book/tutorial that would be great.
Cheers
Just to make it clear, I know that I would need to save the data somewhere and that NSUserDefaults seems to be the best way to do this. What is confusing me is what to actually save when the app is closed and then how to load it so that the correct view is loaded.
NSUserDefaults
http://icodeblog.com/2008/10/03/iphone-programming-tutorial-savingretrieving-data-using-nsuserdefaults/
Obviously, you'll need to save the tab you're displaying, and whatever sort of data storage you want to use to manually determine where the user is. If you're using a drill-down sort of system where your data is in arrays that the user opens, then I would suggest saving an NSIndexPath of the items the user clicked on to get to his current position.
Then, on app startup, first switch to the correct tab, then load the index path and create the necessary views and set the navigation controller's stack using [navigationController setViewControllers:animated:]