Proper use of finished: in setAnimationDidStopSelector:? - ios

I have some playing cards that flip over when you tap them. I want some stuff to happen after the flip animation is complete, so I have this in my UIView animation cycle:
[UIView setAnimationDidStopSelector:#selector(flipAnimationDone:finished:context:)];
...which calls this:
-(void)flipAnimationDone:(NSString *)animationID finished:(BOOL)finished context:(void *)context {
if (finished == YES) {
// other stuff here
}
}
Now, I need the if (finished == YES) bit in there, because otherwise the other stuff will fire even if the user taps the card again mid-animation, which is bad -- it needs to only happen if the flip animation completes completely :)
Problem is, this isn't working. If I have the if in there, the other stuff doesn't ever fire, no matter what. If I leave the if out, the other stuff fires, but possibly at the wrong time.
What am I doing wrong with the finished bit that is making it not work properly?
Thanks!

Notice that the documentation says the method signature is
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;
Specifically,
finished
An NSNumber object containing a Boolean value. The value is YES if the animation ran to completion before it stopped or NO if it did not.
So, you should make it (NSNumber *)finished and if ([finished boolValue]) { ... }.

Related

Get all views of an app- every moment?

I need to find a way to create some class that is always alive in the app. This class doesn't know anything about the other classes in the project. It will be able to "follow" all UIViews on screen-so every moment I can check(loop) over the views and get a pointer to each of them.
It has to run live, and always check positions of views (is it a memory problem?)
Why a pointer? because I need to know everything about it, so if its some kind of moving animation, or maybe it has some meta data like tags, etc. so only knowing there is some view at a certain position is not enough.
Is it possible in iOS ?
The whole idea sounds like an antipattern. However, …
Simply traverse the view tree and add a KVO handler to every view for every interesting property.
- (void)traverseSubviewsOfParentView:(UIView*)view
for( UIView* subview in view.subviews )
{
[view addObserver:self forKeyPath:#"frame" options: NSKeyValueObservingOptionNew];
…
[self traverseSubviewsOfParentView:subview context:NULL];
}
Then implement the observation method:
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if( [#"frame" isEqualToString:keyPath] )
{
// Do what you want to do
}
…
}
Additionally you have to observe the subviews property of every view to get notified, when a view is inserted or removed.

iOS: Animation Not working in observeValueForKeyPath

I used KVO to observe changes in a frame and set another frame accordingly
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{
if ([keyPath isEqualToString:KeyPathForFrameBeingObserved]) {
[UIView animateWithDuration:1 animations:^{
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
self.viewToAnimate.frame = frame;
}];
}
}
Code in the animation block is executed but animation is not working, I suspected that repeated calls to animation method could cause this but using log messages I found that animation is not working even for a single call, can anyone explain that ?
iPad 2/(iOS8.4)
I have tried including the animation method call in dispatch_async(dispatch_get_main_queue(), ^{ ... }); but animation still not working.
I have tried pushing the observed changes in queue and delaying the animation method for a second using dispatch_after to run it only using the last element in the queue to avoid repeated calls but didn't work.
KVO is working perfectly (adding the observer is already handled), but the code in the animation block is executed without animation.
It seems like you're trying to observe the property of an UIKit object. Keep in mind that:
... the classes of the UIKit framework generally do not support KVO ...
Source: https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html
So, you have to check if your observeValueForKeyPath:ofObject:change:context: is calling in fact.
This is an older question but I thought I would answer in case someone else is reading this. You will need to call setFrame to set the frame of the animation instead of the simple assignment.
CGRect frame = [self playWithFrame:viewBeingMonitored.frame];
[self.viewToAnimate setFrame:frame];

Do something on each property change

I have several properties in my class, I would like to call saveToFile on each property change.
I prefer not to override the setter of each property. Should I override
-[NSObject methodForSelector]? What is the best way to go?
You can register as observer to the properties you want monitored. Cocoa's KVO functionality will help you here.
Basically you need to call addObserver:forKeyPath:options:context: and register to be notified when the properties change. When this happens, the runtime calls the observeValueForKeyPath:ofObject:change:context: method on the object registered as observer. You can do here the saving you want to do.
Example for registering:
for(NSString *propName in self.propsIWantMonitored) {
[self addObserver:self forKeyPath:propName change:0 context:#selector(saveToFile)];
}
and for dealing with the change of the prop values:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
// make sure we don't interfere with other observed props
// and check the context param
if (context == #selector(saveToFile)) {
[self saveToFile];
}
}
and for de-registering:
for(NSString *propName in self.propsIWantMonitored) {
[self removeObserver:self forKeyPath:propName context:#selector(saveToFile)];
}
The code samples above assume you have declared an array of properties to monitor, that you use to register as observer to. You use the context parameter to determine if observeValueForKeyPath was called as a response to the observer you just registered, or not, in order not to get into conflict with other KVO registrations made from other parts of your class.
Alternative (and more energy efficient) approach to your problem
There's one caveat with the above approach: if multiple properties are set consecutively, then the saveToFile method will be called multiple times in a short period of time, which might cause performance bottlenecks and increase the energy usage of your application.
An alternative approach would be to have a dirty flag that gets set in observeValueForKeyPath: and gets reset in saveToFile. And you can have saveToFile check the flag and don't go use the file system if the object is not dirty.
You can schedule a timer that will periodically call saveToFile, this way multiple properties set at once will result in only one disk access. You can always manually call saveToFile when you feel want an immediate save.
Note. By timer I was referring to a GCD timer, as NSTimer also has a negative energy impact on your application.
What you want is called Key-Value-Observing or KVO.
You register for example a method that gets called every time the property changes.
If you have a text field and you want to listen to changes to its text, you would register like this
[self.textField addObserver:self forKeyPath:#"text" options:NSKeyValueObservingOptionNew context:nil];
And in your class you would implement this method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if ([keyPath isEqualToString:#"text"]) {
NSLog(#"Textfield changed - MAKE CHANGES HERE");
}
}
Here's a nice tutorial, if you aren't familiar with KVO:
http://www.appcoda.com/understanding-key-value-observing-coding/
Read up on Property Observers. An example in Swift:
var currentSession: Session? {
didSet {
if let session = self.currentSession {
// Write session to file.
}
}
}
For Objective-C, key-value observing might be more proper.

Updating UITableView after download is finished/started

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.

KVO Loading a new view with existing model data

I've recently begun to discover what can be done with KVO and I'm refactoring some of my code and saving a lot of lines at the same time. I do face one issue that is so general that it makes me wonder whether a certain pattern is recommended.
In some cases I load a new view controller that needs to represent data from an already initialized model. On -viewDidLoad I would register for KVO:
[_model addObserver:self
forKeyPath:kSomeKey
options:NSKeyValueObservingOptionNew
context:(__bridge void *)(_model)];
and change my interface when values change:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:kSomeKey] && context == (__bridge void *)(_model)) {
[self updateSomeInterfaceElement];
}
Unfortunately and understandably, the view is not updated with current values from the model when I load my new view.
Is my best option to call -updateSomeInterfaceElement in -viewDidLoad? It doesn't seem to be a big deal like this, but when listening for 10-20 properties, it looks very inefficient (especially since all my -updateSomeInterfaceElement methods are mostly 1 line only, so no need to make them into a separate method). Is there any way to circumvent this, or is there a more elegant solution?
You want to change your options to include NSKeyValueObservingOptionInitial. This will cause KVO to fire a notification when you add the observer, providing the observer with the "initial" value.
Also, as an aside, you should get in the habit of calling super if observeValueForKeyPath:... is called for a notification you didn't sign up for. Also, it's a bit more bulletproof to avoid using "live" pointers in the role of KVO contexts (since a future object could have the same pointer if the current object is deallocated.) I generally prefer to use a pattern like this:
static void * const MyObservationContext = (void*)&MyObservationContext;
- (void)viewDidLoad
{
// ... other stuff ...
[self addObserver:self forKeyPath:#"model.someKey" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:MyObservationContext];
// ... other stuff ...
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == MyObservationContext)
{
// Do stuff
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

Resources