I'm dealing with a tricky situation in my app. This app shows a list of files in a UITableView, from there you can download (I use AFnetworking 2.0) a file and then you can see download progress in another UITableView (all the views are in a UITabBarController).
My problem is that I'm not really sure about how to reload the UITableVIew showing current downloads when a new one is added or when one is finished or cancelled. I tried using KVO but it didn't work observing the operation queue from AFnetworking.
EDIT:
This is the KVO code I tried
In my downloads UITableViewCell
- (void)viewDidLoad
{
[super viewDidLoad];
[[[[PodcastManager sharedManager] sessionManager] operationQueue] addObserver:self
forKeyPath:NSStringFromSelector(#selector(operations))
options:NSKeyValueObservingOptionNew
context:nil];
}
and then...
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == [[[PodcastManager sharedManager] sessionManager] operationQueue]) {
if ([keyPath isEqualToString:NSStringFromSelector(#selector(operations))]) {
NSLog(#"OperationCount: %lu", (unsigned long)[[[[[PodcastManager sharedManager] sessionManager] operationQueue] operations] count] );
}
}
}
But this "solution" didn't work here.
In the past, I faced a similar situation with another app and then I used blocks, but this time, I want to avoid that solution because there is some part of my current app's code I want to reuse in the future.
Anybody has faced a similar situation can bring some light?
Thank you.
Why not just run
[tableView reloadData];
after each download? Maybe I"m missing something. But that's what I'd do.
Related
I'm attempting to create a loading screen that draws a circle while my app makes network requests. The amount drawn of the circle is meant to represent how close the request is to finishing. However there is a serious delay (~8 seconds) between the network request and the animation. After A LOT of searching I haven't found anyone that has had this problem before so I'm very desperate.
My setup right now is that an NSProgress object will be updated as a request is being made and will trigger a KVO Notification with the NSProgress object in the userInfo. This is the same method found here.
#Client.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"fractionCompleted"] && [object isKindOfClass:[NSProgress class]]) {
NSProgress *progress = (NSProgress *)object;
NSDictionary *userInfo = #{#"progress":progress};
[[NSNotificationCenter defaultCenter] postNotificationName:#"FractionCompleted" object:self userInfo:userInfo];
}
}
Then the view controller that is listening for the notification will update the LoadingProgressView with the fractionCompleted of the NSProgress object that it receives.
#MainViewController.m
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateProgress:) name:#"FractionCompleted" object:nil];
...
...
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
}
Now within the LoadingProgressView, the CAShapeLayer's strokeEnd property is set as the fractionCompleted value. I am using the same idea from the tutorial found here.
#LoadingProgressView.m
- (void)updateProgress:(CGFloat)frac {
_circlePathLayer.strokeEnd = frac;
}
When I actually make a request, nothing happens until about 5 seconds AFTER the request is finished. At that point the entire circle is animated at once.
I have ABSOLUTELY NO IDEA why this is happening and it's driving me crazy. I can clearly see using the debugger that the strokeEnd property is being updated in real-time and yet the LoadingProgressView refuses to re-render until much later. Any help is very much appreciated. Thanks.
EDIT: OK so a temporary solution was to fork a new thread with a delay of 0 to update the progress view for each notification. However this seems like poor thread management since I could be creating over a hundred different threads to do the same task. I'm wondering if there's anything else I can do.
- (void)updateProgress:(NSNotification*)note {
NSProgress* prog = note.userInfo[#"progress"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.progressIndicatorView updateProgress:(CGFloat)prog.fractionCompleted];
});
}
I ran into this same problem. It seems to be caused by setting the path from a background thread. Ensuring this happens on the main thread solved the problem. Now updating the shape draws immediately.
I'm trying to figure out how to troubleshoot and/or solve an issue where a UIWebView clearly loads but then disappears.
After tracing in the debugger, the webView is disappearing after viewDidAppear:
The content flashes briefly on screen and is then gone:
To help me trace, I've got some KVO set up to monitor the contentSize:
- (void)viewDidLoad
{
[super viewDidLoad];
[self startObservingChangesInView:self.webView];
}
- (void)dealloc {
[self stopObservingChangesInView:self.webView];
}
- (void)startObservingChangesInView:(UIView *)view {
[view addObserver:self forKeyPath:#"contentSize" options:0 context:&kObservingChangesContext];
}
- (void)stopObservingChangesInView:(UIView *)view {
[view removeObserver:self forKeyPath:#"contentSize" context:&kObservingChangesContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &kObservingChangesContext) {
UIView *view = object;
NSLog(#"View changed for keypath, '%#': '%#'", keyPath, view.description);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Extra context
There's more going on in my UIWebView than shown above, I just wanted to show my current mechanism for monitoring keys.
There are some cases where the load works and stays.
Works
Run app in simulator
Log in
Open Menu
Open Url
Doesn't work
It stops working when following these steps:
Run app in simulator
Log in
Open Menu
Open Url
Logout
Login
Open Menu
Open Url
The problem is that I'm not holding on to any references after logging the user out of the app. The webView is created a new object every time it is pushed with UINavigationController.
So how can I go about solving this? Either there is a known solution or maybe there are there variables I should be tracing with KVO?
Check if your WebView is not being removed from the view hierarchy somewhere by the application. I had similar problem and I've noticed that I unintentionally removed my WebView from the view hierarchy in some event handler.
I faced a similar issue. But the problem with my setup was I was initializing the UI and the view in the Launchscreen.storyboard and not the Main.storyboard. Due to the nature of the Launchscreen.storyboard, it displays my view for some time before vanishing. Putting the view in the Main.storyboard fixed the issue.
I am attempting to accessing related artists/playlists, etc using the SPSearch class as follows, but upon examining the logs it appears that the search never finishes loading:
self.search = [SPSearch searchWithSearchQuery:self.artist inSession:[SPSession sharedSession]];
[SPAsyncLoading waitUntilLoaded:self.search timeout:100.0 then:^(NSArray *loadedItems, NSArray *notLoadedItems){
NSLog(#"Search completed, Loaded items = %d, unloaded items = %d", [loadedItems count], [notLoadedItems count]);
}];
I saw a similar question SPSearch in CocoaLibSpotify but the Key-Value-observer appears to be overkill/convoluted for what I would like to do(maybe it is just my relative newness to iOS dev that makes the KVO seem as overkill/convoluted). Even if it isn't overkill, how would I go about using the SPSearch class to accomplish what I would like to do? It looks like it should be straight forward, but I appear to be getting hung up with some devilish details(search never loads, artist/playlists arrays returning count of of 0, etc).
Edit:
Additionally, I attempted to give the KVO a shot, as opposed to the SPAsyncLoading, but it it has been 10 minutes, and no change seems to have been observed:
[self addObserver:self
forKeyPath:#"search.loaded"
options:0
context:nil];
...
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:#"search.loaded"]) {
if (!self.search.isLoaded)
return;
NSLog(#"artists length %d", self.search.artists.count);
NSLog(#"playlistslength %d", self.search.playlists.count);
NSLog(#"albums length %d", self.search.albums.count);
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
Also, I can rule out a network related issue, I am over Wifi with solid data rates and authentication happens flawlessly.
Thanks.
I have some code which applies to a number of objects, registering my class as the KVO:
for (SPPanelManager *manager in self.panelManagers) {
[manager addObserver:self forKeyPath:#"dataFetchComplete" options:0 context:NULL];
[manager fetchData];
}
Then when it observes a change, which happens on every of these objects, I un-register:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
//Other stuff
}
}
Then when I leave the UIViewController later, I get these errors for each of the manager objects:
An instance of class was deallocated while key value observers were
still registered with it. Observation info was leaked, and may even
become mistakenly attached to some other object.
I'm not sure why it's giving me this error - these are the only 2 places that KVO is ever referenced so it's not another observer.
Your class(observer) is being deallocated during some activity. You must unregister it before it is deallocated or not is in further use. Use code below in viewDidUnload: or dealloc:
for (SPPanelManager *manager in self.panelManagers) {
[manager removeObserver:self forKeyPath:#"dataFetchComplete" context:NULL];
}
Don't try to add or remove observers in observeValueForKeyPath:ofObject:change:context:. KVO expects the list of observers for a given Tuple(object, keyPath, context) to remain the same across a notification for that combination. Even it it works "sometimes" the behavior is non-deterministic because the order in which observers are notified is not guaranteed (it probably uses a set-type data structure internally.)
The simplest way around this problem might look something like:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
});
}
}
This will cause the observation to be removed at the next possible point in the run loop (barring custom run loop modes, which might delay its execution a bit, but usually won't be a problem). Also, you shouldn't need to worry about either self or object getting deallocated because they will be retained by the block closure until the block has executed and is, itself, releases.
As you've discovered, KVO isn't a particularly great API for one-shot notifications.
As to the initial error message, you'll need to remove the observation. You can probably get away with doing this in the observing object's dealloc but you should really avoid doing "real work" in dealloc and removing observations is arguably "real work." Unfortunately, there's not a standard teardown pattern in Cocoa, so you'd have to trigger the teardown yourself, perhaps when you segue out of the view controller, or something like that. Another answer suggested viewDidUnload but that is deprecated in iOS 6 and will never be called, so it's not a good approach any more.
You've to simply dealloc your GMS_MapView Object and as well as remove the MapView Observer forkeypath.
(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[objGMS_MapView removeObserver:self forKeyPath:#"myLocation"];
//=> Set map delegate to nil (to avoid: mapView:regionDidChangeAnimated:]: message sent to deallocated instance )
objGMS_MapView.delegate = nil;
objGMS_MapView = nil;
}
Get notification when NSOperationQueue finishes all tasks
I have the same issue as the one posted by #porneL in the post above. I tried the solution posted by #NickForge (one that received 57 votes), but I am obviously doing it wrong because it does not work for me. Here is the problem setup and my implementation:
I need to start a spinner before kicking off a set of web-service operations, and stop the spinner when they are complete. The webservices are invoked through a shared AFHTTPClient instance (part of the AFNetworking package) which adds them to its NSOperationQueue.
I set up an observer in the ViewController from which the data loads are kicked off. Did this using the answer from the above post. Implementation in my VC looks like:
In my ViewController's init method:
//add WelcomeVC as an observer for AFHTTPClient dataloadOps notifications
[[[MyCustomAFHTTPClient sharedClient] operationQueue] addObserver:self forKeyPath:#"DataLoaderEvent" options:0 context:NULL];
In my ViewController's observeValueForKeyPath method:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if( (object == [[MyCustomAFHTTPClient sharedClient] operationQueue])
&& [keyPath isEqualToString:#"DataLoaderEvent"]) {
if ( [[[MyCustomAFHTTPClient sharedClient] operationQueue] operationCount] == 0) {
NSLog(#"EUREKA!!! QUEUE IS EMPTY! DATALOAD HAS COMPLETED!");
}
}
}
The ViewController's observeValueForKeyPath method however never gets called!
Any help to get this working would be most appreciated so I can then complete implementing the spinner.
Does operationQueue have a property called DataLoaderEvent? Normally one monitors the "operationCount" property of an NSOperationQueue.
See http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html#//apple_ref/doc/uid/TP40004592
"The NSOperationQueue class is key-value coding (KVC) and key-value observing (KVO) compliant. You can observe these properties as desired to control other parts of your application. The properties you can observe include the following:
operations - read-only property
operationCount - read-only property"
try this:
[operation setCompletionBlock: ^{
NSLog(#"Finished an image.");
}];