iOS View Controller Life Cyle - ios

I'm facing some weird behavior in my map based App. I'm fetching some data to display a route using some directions service. It runs in an background thread using GCD. With the data fetched I return to the main thread to update the UI :
dispatch_async(dispatch_get_main_queue(), ^{
[self.mapProvider addToExistingPolyLinePoints:coordinates withTitle:#"line" removeOldOne:NO useCurrentIndex:NO];
[_distanceLabel setText:[NSString stringWithFormat:#"%.2lf km",[self.draggingLogic getOverallDistance]]];
[self.progress setHidden:YES];
});
This all works fine in my RouteViewController. But if I go back to the RootViewController using the back button and reenter the RouteViewController and refetch the whole thing, the UI does not get evaluated. It shows the same behavior as if the UI update is not done in the main thread. The data arrives correct.
I'm wondering if it is some kind of issue regarding the view controller life-cycle of iOS, which I did not got completely. What happens when I push the back button. Obviously the ViewController is not destroyed but if I reenter it will create a new one. Is it possible from the RootViewController to determine if an instance of the target view controller is existing and perform the Segue using it?
Anyway, I'm not sure if this is regarding my issue.
Thanks for any ideas

If I understand right what you wrote, you create a new controller every time you "enter" but the dispatching block always refer to the first one you create, so the new one is displayed but the old one get the notifications...
There are lots of way to avoid this, depends on your implementation, but a simple solution may be keep a (strong) reference to the map view controller in a property of the root view controller: if it's nil (first time) you create the map controller and do all the needed stuffs, else you'll simply show it, without the creation part.
example code, in .h:
#property (strong,nonatomic) MyMapController* mapController;
in .m:
if (!self.mapController)
{
// create the controller and the update handler...
self.mapController = ... //created object
}
// show it and everything...
hope this help

You are needed to do the stuff given here....
you create a new controller every time you "enter" but the dispatching block always refer to the first one you create, so the new one is displayed but the old one get the notifications...
Ex-code, in Interface file :
#property (strong,nonatomic) MyMapProvider* mapProvider;
And in implementation file :
if (!self.mapProvider)
{
self.mapProvider = ... //create object
}
// do your stuff..

Related

WKInterface button doesn't change title

I'm trying to change the title of a button after I call back from a notification but it doesn't respond at all. I checked it's not nil and checked the text Im' assigning and all is good. I made the property type strong instead of weak but no success.
- (void) setButtonTitleFromSelectedSearchResult:(NSNotification *)notif
{
[self popController];
self.sourceMapItem = [[notif userInfo] valueForKey:#"SelectedResult"];
NSLog(#"The Selected Result is: %#", self.sourceMapItem.name);
//Testing
NSLog(#"%#", self.fromButton); // check it's not nil
[self.fromButton setTitle:self.sourceMapItem.name];
}
With WatchKit, if a user interface element isn't currently visible, it cannot be updated. So, if you've presented another interface controller "on top", you can't update any of the presenting controller's interface elements until you've dismissed the presented controller. At that point, you can safely update the presenting controller in its willActivate method.
SushiGrass' method of passing blocks is certainly one valid approach. In my testing, however, I ended up having to manage multiple blocks, and many of the subsequent blocks reversed what earlier queued blocks had accomplished (for example, first changing a label's text to "foo", then "bar", then "foo" again. While this can work, it isn't optimal.
I'd suggest that anyone who is working on a WatchKit app takes a moment to consider how they want to account for off-screen (i.e. not-currently-visible) interface elements. willActivate is your friend, and coming up with a way to manage updates in that method is worthwhile if you're moving from controller to controller.
For what it's worth, I've encapsulated a lot of this logic in a JBInterfaceController subclass that handles a lot of this for you. By using this as a base class for your own interface controller, you can simply update your elements in the added didUpdateInterface method. Unfortunately, I haven't yet had the time to write proper documentation, but the header files and sample project should get you going: https://github.com/mikeswanson/JBInterfaceController
I'm using latest XCode 6.3 and below code working with me.
self.testBtn is bind with Storyboard and its WKInterfaceButton
I also have attached screenshot with affected result.
I'm setting initial text in - (void)willActivate
- (void)willActivate {
[super willActivate];
[self.testBtn setTitle:#"Test"];
[self performSelector:#selector(justDelayed) withObject:nil afterDelay:5.0]
}
-(void)justDelayed
{
[self.testBtn setTitle:#"Testing completed...!!"];
}
If you're using an IBOutlet for the property fromButton be sure that is connected to WKInteface on the storyboard, like below:
I solved this kind of issue by creating a model object that has a property that is a block of type () -> (Void) (in swift). I create the model object, set the action in the block that I'd like the pushing WKInterfaceController to do on completion, and finally pass that model object in the context to the pushed WKInterfaceController. The pushed WKInterfaceController holds a reference to the model object as a property and calls it's completion block when it's done with whatever it needs to do and after func popController().
This worked for me for patterns like what you are describing along with removing rows on detail controller deletion, network calls, location fetches and other tasks.
You can see what I'm talking about here: https://gist.github.com/jacobvanorder/9bf5ada8a7ce93317170

How do I load a view controller in advance if I know the user will most likely select it soon?

In my app, some studies I've done show that when the user views an article, the vast majority of the time (85%+) they load the accompanying comments view controller that goes along with the article.
I'd love to load this view controller while they're reading the article, so when they tap the comments button to transition to the comments view controller the view controller is ready without any loading times.
How would I go about accomplishing something like this? At the moment when the user taps the button I call performSegueWithIdentifier: and pass it the identifier I set in the Storyboard.
And obviously, for the cases where the user decides to go back to the root view controller (say, a list of articles) I'd want to cancel the loading of that comments view controller as it would be wasteful to continue at that point.
If you keep the data model separate from the UI, you shouldn't have any trouble creating the views on the fly, and almost nothing to gain from creating them earlier.
A reasonably standard approach is to bring the view in with blank or filler data, and have the data call go out to the middle tier, with asynchronous handlers processing callbacks.
This is much easier with the block based completion handlers available in the iOS 7 flavored NSURLSession, and only slightly harder with NSURLConnection (which listens for responses on the main thread, but can be thrown into the background once you catch the response).
So my advice would be to focus on backgrounding the data calls and responses, and strongly differentiate between displaying UI and populating the UI with data. If your data manager is separate from your View Controller, nothing is stopping you from "pre-fetching" the data a little early, and then potentially having it ready when the ViewController needs it. It's a perfectly normal load balancing / customer experience technique for high value data.
The solution I'm about to describe is a bit of a hack - it doesn't really conform to the proper model-view-controller design pattern that Ryan mentioned. That being said, it might give you an idea about how to proceed. Perhaps you can improve on it to make it cleaner.
First, define a #protocol in the App Delegate. Let's call this protocol CommentQueryDelegate; it should define a method called -(void)handleCommentQuery. Also give your App Delegate a strong property to store the comment data and a weak property to store a delegate object, like so:
#property (nonatomic, strong) NSMutableArray* arrayOfComments;
#property (nonatomic, weak) id<CommentQueryDelegate> commentQueryDelegateObject;
Make sure to initialize the both of these properties to nil.
Somewhere in the article view controller, use dispatch_async() to asynchronously query your database and retrieve the comments while the user is reading the article:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// query your database here
appDelegate.arrayOfComments = [NSMutableArray array];
[appDelegate.arrayOfComments addObject:#"someComment"];
[appDelegate.commentQueryDelegateObject handleCommentQuery];
}
If the query is completed before the user segues into the comments view, the handleCommentQuery message will be sent to the nil object, which will have no effect.
Now, in the comments view controller's viewDidLoad method, set the commentQueryDelegateObject property of the App Delegate to self. You will need to specify that the comments view controller conforms to the comment query protocol. Next, check to see if the App Delegate's arrayOfComments property is nil. If it isn't, great - display the comments immediately. Otherwise, display a UIActivityIndicatorView.
Implement the -(void)handleCommentQuery method in your comments view controller. This method should disable the activity indicator and display the comments.
One final thing to consider - the strong pointer to the arrayOfComments object will keep it from being deallocated, so you should set this pointer to nil once you're done with your article view controller.

App hangs when property is included in block

I have a UIViewController which displays a table of data that is pulled from an online database. I have a singleton manager to handle the pulling of this data and provide the data the table needs.
This is an example of how the manager works:
#property (nonatomic) NSArray *dataArray;
...
- (void)refreshDataSource
{
[AClass fetchInBackgroundWithCompletionHandler:^(NSArray *objects) {
self.dataArray = [NSArray arrayWithArray:objects];
}
}
...
- (NSArray *)tableViewDataSource
{
return self.dataArray;
}
The view controller requests an update by calling -refreshDataSource in -viewDidLoad but in the meantime provides its UITableView with cache data from the manager by pointing to -tableViewDataSource.
When the view controller presents itself for the first time, everything is fine. The second time I go to present the same view controller, the app hangs. The network request doesn't fire either.
The only fix I've found is moving my -refreshDataSource call to -viewDidAppear: instead. But it itches me why this would be happening and discomforts me that something here must be wrong.
If anyone could provide any help or suggestions that would be great!
Your question hasn't explained everything, but here are a couple of ideas that might help you.
1) viewDidLoad is only called the first time your view loads. If you switch to a different view, then return to your tableView, refreshDataSource will not be called.
2) viewDidLoad might be firing before an array has been allocated and initialised, so it's nil when you're refreshing the data, whereas viewDidAppear might not have the same problem.
I can't give a more concrete answer without more information. Can you explain "the first time, everything is fine. The second time I go..." more clearly? Step-by-step what you do, if possible.

Creating new object in Core Data: create first or upon return?

So I'm struggling with Core Data. I'm finding there are many ways to do the same thing, and that if you try to build the app using Storyboards and UIManagedDocuments you need to read all the tutorials and examples older than last year with a translation sheet. Today's question is about finding the best practice when adding a new managed object. I've seen examples done both ways:
Create the new managed object in a table view controller (after clicking +) and giving that new shining managed object to the subordinate "Add" view controller to get user input for all the object attributes. This seems simple, and the returned object is simple to understand because it contains all the individual attributes. But I've seen example code in this "Add" view controller for a "cancel" button that deletes the managed object passed in and then calls Save Context before dismissing itself. Functional, but the MVC training gnome on my shoulder is screaming at me about having this subordinate Add View delete an object and horrors directly call Save Context. The Recipe example code from Apple appears to use this method.
Send nothing to the Add view controller, and have it send back a delegate call the table view controller that returns each of the attributes as a separate passed parameter. So the return method becomes really long: controller:(UIViewController *)controller didReturnFirstName:(NSString *)firstName andLastName:(NSString *)lastName andStreetAddress:(NSString *) and... and... and.. But this is SO consistent with MVC dogma because the managed object is created back in the table view controller when it receives all the individual attributes, and the "Add" view never touches the Model(Core Data), or throws away an unused managed object when the user changes their mind.
Even with chained delegated methods, I'm still debating with myself which is a better method. Comments and ideas from those who've lived with both forms would be a welcome addition.
Thanks.
It you look at the example in Apple's tutorial, they accomplish this task by doing a number of things outlined below, in this case they have a modal view appear to input the information that is to be added to the data model:
In the modal view that appears, they create a protocol to handle either dismissing the view or saving the data and a property of type id that implements that protocol to be the delagate, this insures whatever object that is implments the required methods
In the view controller that created the modal view, they implement the protocol, including saving the object to the data model and dismissing the modal view
In the view controller that created the modal, they set the view controller that created the modal view as the modal view delegate during the seque to the modal
So, to summarize, in the modal view to collect the new data you need to:
create a protocol and property in .h and synthesize it
#protocol yourProtocol <NSObject>;
//methods that determine what happens based on what user does, it would save your core data object
#end
#property(nonatomic, weak) id<yourProtocol> delegate;
Then, in the modal view .m file you call those methods on the delegate, likely when they pick save or done, so a method for each probably as IBAction connected to a button
[self.delegate myMethod];
In the view that presented the modal view, you implement the protocol in the .h file
#interface viewController() <yourProtocol>
and finally, add your methods to the view controller that presented the modal view's .m file, this should include removing the view and saving your core data. According to Apple and other sources, the view controller that caused the popup/modal, etc... should be the one that dismisses it. Then, in the seque using the seque indentfying, set the view controller that is presented int he modal view as the modal view's delegate.
You're right, there are many approaches to take on this one.
It sounds as if you are starting with a context that may not be saved, so in order to be able to get back to your starting point I would tackle it like this:
Start by creating a new NSManagedObject to be used as a temporary object which gets passed to your "Edit" view.
If you are editing an existing object, copy the attributes from the existing object into this new temporary object (You can use a quick for loop to copy them all generically). Otherwise proceed with the newly created object.
Pass the temporary object to the "Edit" view controller and have it treat both cases the same.
a. If the user presses cancel, have either a protocol or delegate method which notifies the tableview and the tableview then simply discards the temporary object.
b. If they press save, notify the tableview and then copy the attributes from the temporary copy back to the original and delete the temporary object (if it is an edit operation), or just leave it as the newly created object if it was an "Add" operation.
This is not really an answer so feel free to ignore, but neither is any of the others (so far) a complete answer, and I feel the need to add some points without creating a new question, because I really want to get to the bottom of this too. In particular the answer should deal with
what happens when you Cancel vs Save
what if you want to use the same controller for editing an existing entity rather than creating a new one
Nicks answer deals with apple's (current) convention for passing data back, but is not a complete answer because that tutorial does not deal with core-data managed objects, and apples own samples that do use managed objects do it differently.
But on this subject, I find that the delegate convention is unwieldy in some cases and was happy to read this from a more experienced developer:
http://robsprogramknowledge.blogspot.pt/2012/05/back-segues.html
Rob details different mechanisms for that which are all equally valid, including the protocol/delegate convention (Nick's), using a data object without a protocol (so-called "shared memory") - more like some of the others suggested, and - my favourite - using a block.
A block is a nice option because, while it works like a delegate, the code remains entirely in context in the "parent" viewcontrollers source
However, the "shared memory" makes more sense because, as you say, using anything other than the data (managed) object which you've already designed to pass all of these properties around just seems silly.
The issue for me then is, what's the convention for creating these managed objects, passing them around, then either
1. saving changes
2. canceling changes
3. canceling creation of a new entity altogether
Inafzigers answer above works something like this, but why all that copying of properties? Can't I just not save my changes?
Apple' CoreDataBooks seems to deal with this by creating a child ManagedObjectContext and a new entity in that context (insertNewObjectForEntityForName) in the prepareForSegue, and in the delegate method (when it comes back):
- if Save was clicked, it saves both this new ManagedObjectContext and the parent FetchedResultsContext to store the object
- if Cancel was clicked it does nothing, effectively discarding the new managed object context, and thus, presumably, the new object
So this might be the conventional approach, except that:
There is a comment in the code of that sample (CoreDataBooks RootViewController.m) which states that the two managed contexts are not necessary, implying that you might be able to do the same with just one. Here's the full comment:
IMPORTANT: It's not necessary to use a second context for this. You could just use the existing context, which would simplify some of the code -- you wouldn't need to perform two saves, for example. This implementation, though, illustrates a pattern that may sometimes be useful (where you want to maintain a separate set of edits).
CoreDataBooks does not use a UIManagedDocument - not sure if that makes a difference
What's not clear to me is, do we really to use insertNewObjectForEntityForName to create the managed object? What about just creating the object and then only inserting it later if the user hit Save?
Also what about if we do an Edit and a Cancel - can we use the undo manager to get rid of changes?
I think the right approach would actually be to create a new, separate NSManagedObjectContext for your AddController (if a new item is to be created) or EditController (if an existing item is to be modified).
Chose A) or B) depending on your SDK:
A) You then have the choice to either save changes in that context, or discard the context.
In case of a "save", you can merge the changes into the TableController's ManagedObjectContext through Notifications (NSManagedObjectDidSaveNotification).
In case of a 'cancel", you can just discard the separate context.
B) Alternatively, if you're on OSX 10.7(+), you could use nested NSManagedObjectContexts, creating an NSManagedObjectContext in AddController (the child context), and settings it's parentContext to the TableController's NSManagedObjectContext.
In case of a "save", you'd save the child context, and the parent context.
In case of a "cancel", you'd just discard the child context.
See a detailed example of this in Apple's CoreDataBooks example http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Introduction/Intro.html

Saving iOS Application Scene State

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.

Resources