Run a function only on second load of view - objective-c

I am creating an iOS application which has a login system over a tab bar controller. The tab bar is loaded as the root controller and if the user is not logged in, a login subview fills the screen. Because the first view of the tab bar controller is already loaded, so is viewWillAppear:. I use viewWillAppear: to reload a table view on every load. The system works great.
However, I want to know how I can run a function (a void thing) on just the second load (ie. after the user has logged in), to provide a table view specific for the user rather than loading one on every view - this is why I am not using viewDidLoad:, as it only runs once.

Yeah, I have the much better & easy solution rather than taking some variable since the won't retain the last values when we restart our application.
For that you need to use Database but that takes to much of coding.So better to have NSUserDefaults which is used to store the system preferences.
Write this in your viewWillAppear:
if([[NSUserDefaults standardUserDefaults]valueforKey:#"key"]==nil)
{
NSUserDefaults *default = [NSUserDefaults standardUserDefaults];
[default setValue:#"1" forKey:#"key"];
[default synchronize];
}
else
{
NSLog(#"Once you make successful login this value wont change till you make logout");
}
& now just change the value in NSUserDefaults while doing logout.
NSLog(#"%#",[[NSUserDefaults standardUserDefaults] valueForkey:#"key"]); // way to access this system dictionary.
For further reference Google for NSUserDefaults Tutorial
Hope this solve some sort of problem of urs. :)

add an integer instance variable, increment it on each viewWillAppear and run your function only if your instance variable is bigger than 1

If you keep a counter it will not work because user can open your view many times without logged in. So what should you do this keep the user's session information. When the user logged in fill in this session info otherwise not. Check this info every time when the user wants to open this tab.

Related

How UIViewControllers created - by alloc/init vs. performSegueWithIdentifier

My iOS 8 project uses a Storyboard, wherein I have a UINavigationController, with a root UIViewController; let's call this A. I also have another UIViewController, connected via a Segue to this root controller; let's call this B. Pretty standard configuration.
Now, A is a subclassed UITableViewController, and in the table I have a list of URLs. The app user taps a table cell, the URL is extracted and passed to B. B is a subclassed UIViewController that contains a WKWebView.
Now, because B is on the UINavigationController's stack, users can tap the Back button while in B, returning to the table with the URLs (A). The user can then tap another cell, to load another URL.
Because I need to pass some data (the URL) to B, I'm invoking it with performSegueWithIdentifier, and in the corresponding prepareForSegue, I assign the URL to a property in B.
This all works fine. My questions reflect my still-nOOb status with respect to iOS and Objective-C.
The first question, when B is created via the performSegueWithIdentifier / prepareForSegue combo, when the user taps the Back button, is this controller deallocated by the OS? In other words, there's no longer an instance of this subclassed UIViewController floating around?
If it is, then presumably it's safe to have a new instance of it created if the user taps a table cell to view another URL. In other words, repeating the call to performSegueWithIdentifier, assigning the newly chosen URL in B's property, etc. Is it safe?
Or, if it's not deallocated automatically, then does repeating the call to performSegueWithIdentifier, etc. just keep creating more and more instances of B?
And, in either case, does it make sense to, when B is first created, capture a reference to it, and keep re-using it? In other words, before calling performSegueWithIdentifier, check if a reference to B already exists, and if it does, do a pushViewController with that reference to B?
So the basic question boils down to this: In a situation where a UIViewController needs to be made visible many times - through a user action - is an instance being newly created every time it's needed? If it is, then does this also imply that every instance is also being deallocated internally when it's no longer needed? And therefore I don't need to worry about the internals?
Or, if I keep using performSegueWithIdentifier every time, am I just creating a pile of new instances of the controller, and thus being wasteful and inefficient, when I could just capture a reference to the first new instance and keep re-using it?
I've tried to step through this process in the debugger and keep track of references, but I'm unsure whether I'm seeing what I should be seeing, and so I'm asking more knowledgeable developers.
Thank you for reading through this long question, and for your time in answering it.
First of all, when you push view and pop view and if you are not retaining that view controller anywhere else then it will be released completely and it doesn't really matter how many time you call performSegueWithIdentifier to create this view if you are not retaining it or manage it reference well enough.
However, in this case, I would create your 'B' view controller once using
self.bController = [self.storyboard instantiateViewControllerWithIdentifier:#"BController"];
And then you call pushViewController using self.bController, function would be looking something like this..
- (void)presentURL:(NSString*)url
{
if ( self.bController == nil ) {
self.bController = [self.storyboard instantiateViewControllerWithIdentifier:#"BController"];
}
[self.bController setURL:url];
[viewController.navigationController pushViewController:self.bController animated:YES];
}
And refresh the WKWebView with below code when it sets the URL
-(void)setUrlString:(NSString *)urlString {
// set url string
_urlString = urlString;
// clear
[self.webView loadHTMLString:#"" baseURL:nil];
// load url with string
if( _urlString != nil ) {
// get address for hosting view
NSURL* url = [NSURL URLWithString:_urlString];
NSURLRequest* urlRequest = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:urlRequest];
}
}
it will make bit simpler than prepareForSegue, however, again, repeating the call to performSegueWithIdentifier, assigning the newly chosen URL in B's property, etc. It is totally safe as long as you don't retaining anywhere else.. but performance wise it might be bit expensive than keeping one instance of B viewController and showing it multiple time because it has to read and initiate the view controller and the WKWebView instance so arguably it is bit expansive..

Make UITableViewController cells not disappear when navigating away

I'm making an app that uses a UITableViewController, and fills that table view with data from a webserver.
Inside my viewDidLoad I have a method that loads data from said webserver, stores it in an array as custom objects, and then loads that into cells. This is working fine.
Problem:
However, every time I navigate away from that UITableViewController, and then back, it loads everything again. This is very unnecessary, so what I did was store a boolean in the NSUserDefaults, which keeps track of whether or not this is the first time starting the app. If it is (you just logged in), load the data from the server. If not, don't.
However, as I noticed, the tableView resets every time I navigate away from (or back to) the Controller. Also, all the arrays I stored the custom objects in are now empty, so I can't load it back from the arrays either.
(Every time I navigate back to the TableViewController, it's empty)
I tried storing the arrays in the NSUserDefaults, and then just populate the tableView with that data every time, but it turns out I can't store custom objects in the NSUserDefaults.
What I want to achieve is this:
Whenever I navigate away from and back to said TableViewController (I use the SWRevealViewController), I don't want the tableView to empty out. I want all the cells to stay, that way there is no wait time between when the view is loaded and the tableview is filled.
If this is impossible, I want the second best. To store the array somewhere in the app, and then reload that data into the tableview as soon as the user navigates back to the TableViewController. This is slower than my preferred solution, but still quicker and less data-consuming than loading everything from my server.
Any help is appreciated!
Thanks.
You should create a separate object that manages fetching the data from the web service and then stores it locally. This object can be created in the app delegate or wherever appropriate and passed to the table view controller. The data object should provide an array that the view controller can then use to populate the table. You can even have that data object start pulling from the web service as soon as the app opens instead of waiting for the table view controller to be displayed.
I do not recommend keeping the view in memory just to save the very minimal amount of time it takes to load up a table view (using locally stored data). Unless you are talking about thousands and thousands of entries, you will not notice a lag time in the loading of the view. If you are talking about thousands and thousands of entries, I recommend you load a few hundred at a time into the table.
As far as storing the data, the simplest might be just writing the raw response of the web request to a file. A more elegant solution would probably include creating some objects to represent the data and using NSKeyedArchiver. Keeping data stored locally is a huge topic with countless options so I recommend doing some googling on your own to find the best solution for you. You might start at these places:
NSKeyedArchiver: http://www.raywenderlich.com/1914/nscoding-tutorial-for-ios-how-to-save-your-app-data
Other Options: https://developer.apple.com/technologies/ios/data-management.html
If you go with the NSKeyedArchiver option, you can generate a file path by doing the following:
+ (NSString *)dataFilePath
{
NSURL *directory = [[NSFileManager defaultManager]
URLForDirectory:NSLibraryDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:YES
error:nil
];
NSURL *fullURL = [cachesDirectory URLByAppendingPathComponent:#"a_file_name"];
return [fullURL relativePath];
}
You need to store all the data in cache at first time when user is calling data from server. And after that whenever user navigate and comeback to the page load data from cache.

How do I prevent NSMutableArray from losing data?

The first view of my app is a UITableView.
The user will choose an option and the next view will be another UITableView. Now the user can tap on an "add" button to be taken to another UIViewController to enter text in a UITextField. That text will then appear in the previous UITableViewCell when saved.
The issue I am having: if I back out to the main view and then go back to where I previously was, that inputed text is now gone.
How can I make sure that text is not being released or disappears like this?
You might want to store this array somewhere else in your project, like in an MVC (data model). You could create a new class for it that passes the information through the classes and stores the array in one place. Then once you add to the array, you could reference that class and call a method in that class to store the text in the array and whenever you load the table view it loads with that array in the class.
In my case, I would do this, but I would make everything class methods (where you cannot access properties or ivars) and just store the array in the user defaults / web service or wherever you need and retrieve and add/return it like this:
+ (NSMutableArray *)arrayOfSavedData {
return [[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"];
}
+ (void)addStringToArray: (NSString *)stringFromTextField {
[[[[NSUserDefaults standardUserDefaults] objectForKey: #"savedData"] mutableCopy] addObject: stringFromTextField];
}
The mutableCopy part is important because arrays don't stay mutable after you store them into the user defaults
The reason the text is gone, is probably because you're instantiating new controllers when you go back to where you were. You can keep a strong reference to your controllers, and only instantiate one if it doesn't exist yet. Exactly how to do this depends on how you're moving between controllers -- whether you're doing it in code, or using a storyboard for instance.
This kind of issue is very frequent. When you move around multiple controllers and views.
Each time you load a new view and controllers are alloc+init, new set of values are assigned and previous values are not used!!!.
You can use SharedInstance/SingletonClass so that it is allocated & assigned once and does not get re created with new set of values.

iOS App - Refresh last webView on applicationDidBecomeActive

I have a single view iOS app, that contains a webView that displays my website. When the app is returned to from multitasking (when it is brought back as the active app) I would like the webView to get the URL it is currently on, and refresh (or reload) that current page in the webView (I'm thinking using this method):
- (void)applicationDidBecomeActive:(UIApplication *)application
{
NSLog(#"applicationDidBecomeActive");
}
How would I do this? Do I need to subscribe to some sort of notification? Please help point me in the right direction, and provide some code.
There are some point to get noticed are like:
Application should allow user to run in background mode. You can define this in info.plist
Implement UIWebViewDelegate in your view controller. There is one method: webViewDidStartLoad:
Inside that method get the current url
NSString *currentURL = [webView stringByEvaluatingJavaScriptFromString:#"window.location"];
(For more details refer this.)
Save this url string to some where, and while relaunching your application the method which you mentioned in the question gets called. Inside that method, load your web view with this url string.
Hope this is what you required.
Enjoy coding :)

iOS settings toggle switch works the first time but not again

I am using the iPad settings app to change some button sounds and a background image. It all works well and the settings are maintained from one app launch to another in the simulator. Now I have implemented a toggle switch to either set sets of sounds off or on. When the app launches, whatever state the switch is in, it works; e.g. if the "Alert Sounds" switch is OFF the alert sounds are silent and if I change it to ON the sounds will start working. However, if I turn the switch back OFF the sounds still keep working. However, if the state is ON when the app launches, the sounds work, but will not be silenced when the switch is set to OFF.
Note that this is different than the settings not taking effect until a second round of settings. That was a previous problem I solved (thanks to stack overflow) by using:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[[NSUserDefaults standardUserDefaults] synchronize];
}
I have methods named:
- (void)defaultsChanged:(NSNotification *)NSUserDefaultsDidChangeNotification
(which is called when the notification is sent)
and
-(void)setValuesFromPreferences
(which is called in ViewDidLoad)
The logic looks like this in both:
// Set alert sounds from preferences
NSString *alertSoundPreference = [userDefaults stringForKey:kAlertSound];
BOOL alertSoundEnabled = [userDefaults boolForKey:kAlertSoundEnabled];
if (alertSoundEnabled)
{
// Create the URLs for the alert audio files
// Store the alert sound URLs as a CFURLRef instances
// Create system sound objects representing the alert sound files
}
I do not have an else, because I assume that no sound resources will be specified if alertSoundEnabled is NO.
I have searched for explanations and tutorials that mention this problem but have not found any yet, so I'm asking here. Thanks for any suggestions.
viewDidLoad is not necessarily called when the app becomes active again (nor does viewWill/DidAppear, IIRC), as the whole point of iOS 4+ multitasking is to prevent such loading/unloading and recreation of objects on app-switching.
If I had to guess, the sounds are already allocated when the user had the switch ON at original launch/viewDidLoad; however, if your code does nothing to explicitly disassociate them when it loads back up, they would continue playing, as they are all already set up.
As such, I'd try adding an else clause that (upon alertSoundEnabled == NO) destroys your system sound objects.