App hangs when property is included in block - ios

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.

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

Creating TableviewController and presenting it

I'm developing IOS messanger app, I have inbox(tableview) in which I have cells(conversations) and when I select a conversation, I would like to present this conversation(tableviewController full of messages), but i dont like how much time it takes to present this controller. So my idea was to create whole controllers(tableviewController full of messages) objects before selecting conversation, and then just push them. First time I select conversation, it is blank, after going back and then selecting it again, it work. Problem is obvious, some variables are initialized in viewDidLoad method. I have tried to move them to init method but then every time conversation was blank.
Do you have any experiences with this? Any hint will be appreciated a LOT.
Thank you!!!
in tableviewController full of messages:
.h file:
#property (nonatomic, assign) BOOL firstAppear;
.m file
self.firstAppear = NO; //in init method
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if (self.firstAppear) {
//add a indicator view here
}
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (self.firstAppear) {
//get tableView data here, then [tableView reloadData] to show data
//remove the indicator
self.firstAppear = NO;
}
}
It sounds to me like you are doing premature optimization. Creating and pushing a table view controller should take a small fraction of a second. If it's taking longer, something is wrong. Are you loading the conversation data from a remote server or something?
You might want to use Instruments to figure out what is taking extra time and causing a delay. Then you can focus on the actual cause rather than guessing.

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.

iOS - NSMutableDictionary reverting to previous state?

I'm trying to make a "Delete" button on an "Edit Item" page, which when pressed will go back and remove the relevant entry in an NSMutableDictionary on the previous page, and then update the tableview list of entries.
It starts with an IBAction method on the "Edit Item" page, which basically does nothing but feed the key to be deleted back to the controller for the previous page:
- (IBAction)deleteItem:(id)sender {
[_parentController removeItemWithDeleteButton:[_mainFactoidTextField text]];
}
which is where the action actually starts (on the list page):
- (void)removeItemWithDeleteButton:(NSString *)key {
[_currentItemsDict removeObjectForKey:key];
[self.navigationController popViewControllerAnimated:YES];
}
At the start of that method, _currentItemsDict has 8 objects, which drops to 7 after removeObjectForKey: is called. Then it pops off the top ViewController, which is the "Edit Item" page, returning us to the list of entries.
Then when that method is done, the breakpoint immediately jumps to:
- (void)viewWillAppear:(BOOL)animated {
NSArray *tempArray = [NSArray arrayWithArray:[_currentItemsDict allKeys]];
NSArray *sortedArray = [tempArray sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
_currentItemsArray = [NSMutableArray arrayWithArray:sortedArray];
[_tableView reloadData];
}
But by the first line of viewWillAppear:, _currentItemsDict has reverted back to 8 objects. Everything works fine from then on, but it's all working with the original 8 entries, meaning that nothing got deleted.
From my limited experience with these sorts of things (I'm definitely still a beginner), I'm going to guess this has something to do with the popViewControllerAnimated: method, but I cannot figure out what. I've heard distantly of certain variables being stored in different ways that might make them come back as an earlier version of themselves, but I haven't wrapped my mind around the concept. Or maybe it's something else entirely, I don't know. All I can tell is that _currentItemDict has 7 objects, and then one line later it has 8 again.
Can anyone help out a new guy, and explain where I'm going wrong? Or if it's easier, can you suggest a better solution for removing an entry in an NSMutableDictionary from a different ViewController? (Code samples and tutorial links are much appreciated, as I'm currently just noobish enough to sometimes be unable to translate theory into practice on my own!)
Thank you in advance for your help!
Do you know how properties work in Objective-C 2.0? I would declare a property in your parentViewController to contain your _currentItemsArray, declared as a strong reference.
#property (nonatomic,strong) NSMutableArray *_currentItemsArray;
Also, set a weak property in your childViewController (the one you push to for deletion)
#property (nonatomic, weak) NSMutableArray *parentItemsArray;
Set the property on the childViewController upon push
childViewController.parentItemsArray = self._currentItemsArray;
[self.navigationController pushViewController:childViewController animated:YES];
Remove the item inside the array within childViewController instead of calling a method on your parentController
[self.parentItemsArray removeObjectAtIndex:indexOfKey];
Doing this, you don't have to sort the array again in viewWillAppear. Instead, just ask the tableView to reloadData
Does any of this make sense?
It also may be wise to include your logic inside cellForRowAtIndexPath, numberOfRowsInSection and the other tableViewDataSource methods.

iOS View Controller Life Cyle

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..

Resources