ios updating view after it disappeared - ios

I think there is a simple answer to this question, but I can't seem to find it.
I have a view that I would like to keep updated, even after it disappears. It's a music player so it would be nice to be able to go back to the player and see the current track/art/progress without it having to update after the view loads.
I have a strong reference to the view, so it doesn't fully reload, but I can't seem to make updates to the UI elements anywhere outside the view. I have a class that is calling this, but it's completely ignored:
dispatch_async(dispatch_get_main_queue(), ^{
[playerViewController updateAlbumArt];
[playerViewController updateSongTitle];
[playerViewController updateSongArtist];
});
Is there some way to keep a view updated in the background from another class? Similar to how if a track changes on your iPhone music app, you go to the now playing screen and you see the current song progress/information without any lag or loading period.

viewWillAppear is your friend here. You should probably also avoid keeping a strong reference to the originating view controller as that could result in a retain cycle. Keep a weak reference to the parent from the child or consider creating a protocol and set the parent as the child's delegate (weakly referenced). And have the child communicate changes to the parent via the delegate reference.

Related

Best practices for implementing an iOS download manager with progress view

I have a UITableViewController displaying a list of audio files, when tapping on an audio file I push another UITableViewController showing a detailed view of the audio file. This view also features a "Download" button and a UIProgressIndicator view.
I have a Download Manager class (implemented as a Singleton) which takes care of downloading the file. Its download method takes a block which is called with updates about the download progress. I'm using it to update the UIProgressIndicator view. This works fine up to the point where you leave the detail view controller and come back at a later point in time when the file is still downloading. Of course, the progress block specified earlier is still available, but the referenced UIProgressIndicator view inside of it is not, thus it is not updated anymore.
I'm wondering, if it's a sensible idea to just re-set the block upon re-entering the detail view controller (viewDidLoad) or if the block-based approach is not really suitable for this case? Maybe it'd be better to use KVO?
Any suggestions?
Thanks!
The block approach is useful if the lifespan of the downloader is controlled by the VC. This way, when the VC is released, it releases the downloader (the downloader would not be a singleton).
If not you risk creating captured objects (the VC) that cannot be released because they are referenced in your block, and your block is referenced by an "eternal" object (the singleton).
As the VC lifespan is potentially shorter than the downloader, a better option would be to use some subscription-based observing of the downloader singleton.
This way your VC subscribes in (e.g.) viewWillAppear and unsubscribes in viewWillDisappear (important).
You can also use a global progress notification (via NSNotificationCenter), key-value observing or any other means.
The important part is that when your VC is released, nothing in the downloader points to it.

Detecting if view still exists or active in completionHandler block

In my app I'm polling a web service for status updates, using a completionHandler block and making changes to the current view based on returned results when the callback executes.
- (void) tickTimer
{
[MyWebService myWebMethod:param1 completionHandler:^(NSString *result) {
// does view still exist?
[self myUpdateMethod];
// does property still exist?
self.theResult = result;
// does child view still exist?
_txtUpdate.text = result;
}];
}
But in the interim, it's possible that the view may have been unloaded as the user navigates elsewhere.
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to the background? I imagine it gets garbage collected at some point, but how do I tell if it's still safe to access by any of the references above, and what would happen if it's not?
If the view does still exist, how do I tell if it is also still the foreground view?
So, blocks create strong references to all objects pointers that are referred to in their closure. Due to this, your block is going to force [self] to stay in memory until the block is destroyed. If this isn't the behavior you want you should create a weak pointer to self and refer to it inside of the block:
__weak typeof(self) weakSelf = self;
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to
the background? I imagine it gets garbage collected at some point, but
how do I tell if it's still safe to access by any of the references
above, and what would happen if it's not?
If your view stays in the view hierarchy, it will stay in memory. Once there are no more references to the view it will be dealloced.
If you use a weak pointer like outlined above, then [weakSelf] will be nil if the view has been dealloced
If the view does still exist, how do I tell if it is also still the
foreground view?
I'm not sure what you mean by foreground view, but if you want to see if it's still in the view hierarchy then you can check the property -(UIView *)superview. If superview is nil, then it's not on the screen
If you use ARC right, it will not let you use deallocated viewcontroller.
You can use viewDidAppear and viewDidDisappear methods to know visible yours viewcontroller or not.

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.

How can I keep track and control which views deallocates?

When my app is launched, it starts out at a custom UIViewController with a custom UITableView with custom UITableViewCells. The cells can play videos, among other things. I'm using AVPlayer and AVPlayerLayer to play the videos. This initial view controller also has a menu-popup, which can take you to another controller. When another menu-option is clicked, the initial UIViewController deallocates. This is on purpose, and setting the new UIViewController as the applications rootViewController. However, if I start playing a video in a cell in the initial viewController and then click a menu-option, the video keeps playing in the background, I can still hear the audio.
I have simply put
-(void)dealloc{
NSLog(#"Initial controller deallocated");
}
in the initial view controller to confirm that it deallocs when I set the new controller as root, and this prints out, but the video still keeps playing.
I have now 'overridden' the -(void)dealloc(like above) methods of both the custom UITableView and the custom UITableViewCell's, but neither of these fires.
I have simplified the explanation of the hierarchy here, and I believe there could be other connections holding on to the tableView(mostly because I don't know any other reason for this behavior).
I believe, however, that these possible connections all are under the initial view controllers hierarchy, and it is my understanding that the entire hierarchy under a view controller will vanish when the view controller does. Is the AVPlayer a reason the tableView and/or cell won't dealloc? The videos playing are over internet, and I can also tell that the app continues downloading and buffering the video after the controller has been deallocated. I have tried setting the player, the playerLayer and the entire cell to nil, but it keeps downloading, and I'm thinking I have misunderstood some connections.
First of all, I think properties with (strong, nonatomic) or (nonatomic, retain) might have something to do with these. I don't completely understand where to use what, so I almost exclusively use this. Of what I'm reading, weak seems to be correct for some of these references, but I'm also 'scared' of the implications, as I don't completely understand them.
Second, in my custom UITableView, I have a property: #property CustomCell *playing;.
When I start playing a video in a specific cell, like this;
[someSpecificCell.player play]; I also do this: self.playing = someSpecificCell;. Now I can later easily find the cell that's playing, I.E to stop it. When I later try to deallocate the cell, I use self.playing = nil;, however, this does not deallocate the cell. Why not? Am I simply removing the reference from tableView? Does this mean there are other references?
Is there a way to see a list of where these references are /from? As far as I know, all references are in the same initial hierarchy, yet somehow some parts of the hierarchy stays alive after their controller has deallocated.
Update
I've now tried to launch Instruments with the Allocations-tool, and I don't completely understand what I'm seeing.
The hierarchy I've created is something like this:
MyNavController1->MyViewController1->MyView1->MyTableView1->MyCell1->text&video etc.
When I click another menu-option, I create another (almost identical) hierarchy, setting the new MyNavController(2) as rootViewController for the app. The 'current' hierarchy will now be
MyNavController2->MyViewController2->MyView2->MyTableView2->MyCell2->something else
When I present the new hierarchy, and set this new hierarchy as rootViewController to the app, the first hierarchy is supposed to be deallocated. In -(void)dealloc of both MyNavController1 and MyViewController1 print out "Deallocated" with NSLog();, and nothing from MyTableView1 and MyCell1.
In Instruments it says that MyViewController is #living: 1, #transient: 1, which I'm guessing means the first deallocated correctly.
When I check the field for MyTableView, it says #living: 2, #transient: 0, which I'm guessing means the tableView isn't deallocated, as my previous "research" found.
However, in the list showing the two instantiations of MyTableView, it says that the responsible caller of the first tableView(which is supposed to be deallocated, but isn't) is the first MyViewController1, which is confirmed deallocated. What does this mean?
If I further click the Address field of this specific MyTableView(the first), it says:
# Event Type ΔRefCt: RefCt ... Responsible Caller
0 Malloc +1 1 ... 'MyViewController1'
What does this really mean, and can I find out where a possible reference is held?

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