I have 2 views as shown in the following diagram. First View is a ViewController and the 2nd is a TableViewController.
After the user installs and runs the app for the first time he/she will see View number (1) (as shown in the image). Thereafter when the user opens the application for the 2nd time he/she is suppose to see the second (2) viewcontroller.
How am i to program this ?
NB: For example in Viber the user will first see the Enter the phone number view controller, and once the user successfully logins he'll directly see the all contacts view. I am looking to implement the same functionality. Can someone tell me how this is done ?
Solution i came across is when the user successffully logins, i set a NSUserDefaults.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:loginSuccess forKey:#"yes"];
[defaults synchronize];
Thereafter i check if this is set in the didFinishLaunchingWithOptions method. Is this the correct way to do this ?
Using NSUserDefaults is a great way to keep track of which view to show. To actually show the correct view you make the UITableViewController the root view controller for your app. Then in its viewDidLoad you check the value you stored in NSUserDefaults. If you need to display the one-time-only controller then you can simply use presentViewController:vc animated: NO. This will make it appear to the user that they started at the one-time-only controller, but for the rest of time you will have the "normal" flow work as expected.
Check for the second call by using NSUserDefaults. Upon start check for a value for any key. When that is null then it is the first time after installation, that the app is executed. After that store something with the very key.
You can do the check in your application delegate (in application didLodWithOptions) and then show either this or that.
Or you perform the check in viewDidLoad in your tableview controller. You can simply push to the other view (or display it modally). This is quite convenient when you want the user to return to the tableview as soon as his introduction is over. Then simply discard the other view ontop of the tableview.
Related
I have a pure dynamic page page1VC (which is parent) where i have few buttons which takes them to page2VC, page3VC, page4VC
There is next button and back button in every view controller (no back button in p1VC as this is parent)
I can navigate to any viewController from any other vc i.e, from p1VC -> p2VC -> p4VC -> p3Vc
on tap of back in any view controller, that takes me to p1VC
page1VC gets all info to display from API and stores in variable p1APIInfo
On tap of back button from page2VC or page3VC or page4VC --> this should take me to page1VC
My question is:
As my page1VC is pure dynamic, on 1st landing of this page, i get all info from API
When I navigate to page4VC and on tap of back it should take me to page1VC and here i dont want API to be called anytime when user taps back button
As of now i have a dummy variable in page1VC which is copy of p1APIInfo variable (say dummyAPIInfo)
Each time when i tap next im passing that variable to another VC, and on tap of back from that VC im passing back that variable. So basically this variable is to only transport data to other VC and get it back to page1VC on tap of "back" button. Im not using any data in this dummy variable in page2VC/page3VC/page4VC
However this works, but this is not best practice.
I can think of saving my page1 data in userdefaults/singleton but im looking for more optimizing solution
pls suggest how to handle
So to summarize: I want my API call to happen only once i.e., during 1st time landing..after that whenever user visits/tap back button from some other page, i need a way not to call API. It should use same data that user gets during 1st API call.
Hope I'm clear with my question. Pls advice
Make sure you do the calls in a function that isn't called multiple times. For instance, if your api calls are in the viewWillAppear method, you'll do a fetch every time you pop your navigation stack. Putting the api calls in viewDidLoad will ensure that your api calls only happen once.
As for a way to transport data back to page1...
You could create a custom model that page1 relies on, and inject that model to other pages
You could use the delegation pattern to pass back information
You could use notifications and observers to broadcast and catch updates
You could pass a reference of page1 to any other page and allow other pages to make updates to page1 on its behalf
All 4 strategies are viable, and really depends on your use case and what other things you need in your app. You could even use a singleton. UserDefaults seems like a bad idea though, since your use case is to update a model for a view controller.
Apologies, if this is a little unclear - I'm a noob when it comes to iOS programming. Here's the scenario:
I've got a LogInView, a CategoryView, a CheckerView, a WalkthroughView, and a LandingPageView.
The user starts at LogInView, and depending on the app's bluetooth state, and whether or not the user has been registered, either goes to:
CheckerView (Registered, Bluetooth Off)
LandingPageView (Registered, Bluetooth On)
CategoryView (Unregistered)
If the user hits CategoryView, depending on the state of his bluetooth connection, he goes to either (this part, so far, works okay):
WalkthroughView (Bluetooth on)
CheckerView (Bluetooth off)
The catch is that CategoryView will always go through WalkthroughView, regardless of whether or not bluetooth is on. So, here's what my storyboard looks like:
A right hot mess, I know. Since both LogInView and CategoryView can, at some point, go into CheckerView, I need a way to check which of the segues was used, such that:
CheckerView will always go into LandingView if the previous view was LogInView, and
It will always go into WalkthroughView if the previous view was CategoryView.
I'm vaguely aware of a prepareForSegue function, but I've no idea yet how to use it, nor where to put it (from the previous page, or on the receiving page?)
Any suggestions? Thanks.
It sounds like you're testing conditions to determine where you'll segue. If that's the case, perhaps you could test conditions (registered/unregistered, Bluetooth enabled/disabled). Based on the various conditions, you could use performSegueWithIdentifier to determine where to go next and set up the next ViewController in prepareForSegue using the a segue identifier, rather than "looking back" to see where you came from.
My standard suggestion is that once application state becomes complex it should be moved out of view controllers and into an actual Data Model object.
The Data Model can either be a custom class you create (preferred for scalability). Or, in this case where there's not a great volume of information being shared, you could look at putting it into NSUserDefaults and reading from there when needed.
I recently figured out with some help here how to restore my text fields, button states, etc. when putting my app into the background, terminating, and restarting.
What I am now trying to figure out is how to keep the same information when going back to my main menu using the back arrow in the navigation bar. Of course, when I have filled out info on my sub view, hit the back button to go to the main menu, all of my user fields and button states are reset to their initial state.
I am not sure what code you might want to see here. I am happy to provide anything, but I am not quite sure what would be relevant.
My app set up is very simple. It looks like this:
--->navigation controller---->main menu----->calculator
Main menu is embedded in the navigation controller. I just need to be able to go back to the main menu from the calculator without losing the data the user has entered into the fields in th calculator.
Thanks in advance.
EDIT - This is what I am using to save and restore data for general state preservation / restoration - code added:
-(void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
[coder encodeObject:_startLevel.text forKey:#"startText"];
[super encodeRestorableStateWithCoder:coder];
}
-(void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
// start level text
_startLevel.text = [coder decodeObjectForKey:#"startText"];
[super decodeRestorableStateWithCoder:coder];
}
There are multiple ways to store/restore data..
You can make a Singleton class and store/retrieve data in/from
that.
You can store/retrieve data in/from NSUserDefaults
You can store/retrieve data in/from databases e.g., sqlite,
CoreData
You can store/retrieve data in/from files like text files, plist.
files.
It's up to you, what suits you the best. However, you can save data in either viewDidUnload or viewWillDisappear and retrieve that in either viewDidLoad or viewWillAppear of your Calculator View Controller.
I recently figured out with some help here how to restore my text
fields, button states, etc. when putting my app into the background,
terminating, and restarting.
So it seems like you have a mechanism for storing and retrieving data.
Now when you hit back to mainmenu, you can just save the data in viewWillDisappear delegate and restore data in viewWillAppear method of calculator controller.
Hope this helps.
I'm writing an app that let's the user know when they have a new tip waiting at the same time everyday. I've been able to schedule the notification and write the code for it to fire if the app is running in the foreground. What I'm having trouble with is how to get the same thing to happen when the app is in the background. The complication is that I'm having the view be set up (which buttons are visible etc.) based on the notification itself, as the next step is to have a second notification which would have the view set itself up differently. At the moment, my code in the method looks like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UILocalNotification *note = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
NSString *val = [note.userInfo objectForKey:1];
if ([val isEqual: #"first"]) {
}
return YES;
}
My issue is that I have no idea how to set up the view (i.e. how to make two of the buttons [call them button1 and button2] visible within the view) from within that if statement. Any answers would be greatly appreciated, and if this is something unbelievably obvious, then I do apologize and ask only that someone at least point me in the right direction. Thanks
Not 100% on this but believe that
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions
Finishes before starting to load the views, well it makes more sense this way.
Having this in mind, and been used with success by me, i advise you to set up a public var on app delegate that stores the information you need.
After that is just checking in the
- (void)viewDidLoad
Of your viewController if the var as anything and treat the information as you would when the app is in Foreground.
I will try to give you a more general answer to your problem: iOS programming is based on the MVC (Model-View-Controller) software architecture pattern so you must make your application to be as much as possible compliant with MVC. This means, in your case, that as soon as you receive a local notification, whatever your app is running in the foreground or sleeping in the background or simply ready to startup from scratch, the app will behave in the same way if the MVC bricks are properly setup.
So:
the local notification provides some new data to the app, so you must update the model
once you update the model you notify the app view controller of the model change
the view controller if visible will reflect the change in the view that represents the model, if not visible your app must show the view controller and then refresh the view based on the model status.
Clearly how to do this depends on how and where your app shows the different views, but to keep things simple you can:
create the model that reflects the local notification at startup (application:didFinishLaunchingWithOptions:)
create the view controller that show the local notification data at startup (note: you "init" the view controller by providing it the model, you don't care the view yet)
then handle the local notification after the model and view controller have been initialized
So when you receive the local notification the app:
updates the model (which exists in all three cases when the app is in foreground or resuming from sleep or starting from scratch)
at this point if the view controller view is already visibile then you simply refresh it with the new model data, if not visible you add the view controller view in your view hierarchy (e.g. by pushing it in your main navigation controller stack); the view refresh will be done by viewDidLoad:
As you can see you don't need any special code to manage this, apart the need to show the local notification related view when the app gets the notification. Clearly things are much more complex if your local notification view is deep in your view hierarchy and you must maintain some consistency: in such case you must rebuild the whole hierarchy and the possibility to do this depends on the information provided by the local notification.
In the simple case, that is when you can place your local notification view controller just inside the main window hierarchy, after you instantiate your main navigation controller:
[applicationWindow setRootController:mainNavigationController]
you will push your local notification controller in the nav controller stack:
[mainNavigationController pushViewController:myLocalNotificationViewController animated:YES]
Sorry if I'm not entering in the detail of your specific problem (I cannot) but probably my explanation will help you in letting you understand that your case is not a special case but can be easily managed by carefully following the main iOS architectural pattern.
This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.