I want to insert UIViews dynamically with animation, based on incoming Data.
I send two asynchronous URLRequests and get the received data back from another thread.
I'm observing a property, which gets dynamically filled with data from different Requests. So KVO sends me messages, probably directly after each other.
With the first incoming message, I start a few dependent animations. During the animation time it often occurs, that new data arrives and messages my observer. I then want to wait until the animations are over and start the animations for the new data.
I'm looking for a nice implementation, not something with a while loop, waiting until a bool changes.
EDIT:
Consider that I cannot say, which data comes in first. So I cannot define a animation method as the first one.
How about adding the incoming views to a mutableArray as they come in and then use something like this:
-(void) animateView:(UIView *)viewToAnimate{
[UIView animateWithDuration:0.5 animations:^{
viewToAnimate.alpha = 0;
}
completion:^(BOOL finished){
[_mutableArrayOfAnimationViews removeObject: viewToAnimate];
if ([_mutableArrayOfAnimationViews count] > 0) {
UIView *newAnimationView = [_mutableArrayOfAnimationViews objectAtIndex:0];
[self animateView: newAnimationView];
}
}];
}
Then you can also check when the views come in whether the array is already empty, and if so call the above method...
How about create a mutable array that's your kvoToDoList for animations. Put whatever info in there you need to launch the animation (like the object that got the kvo triggered).
Then when the kvo is observed, add the object to the array and call an animation function like this:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
self.kvoToDoList insertObject:object atIndex:0];
// not sure here what you need to remember in order to do the animation, let's say it's just the object
[self doAnimations];
}
- (void)doAnimations {
if (self.kvoToDoList.count == 0) return;
id object = [self.kvoToDoList lastObject]; // FIFO since we insert at index 0
[self.kvoToDoList removeLastObject];
[UIView animateWithDuration:someDuration delay:0.0 options:someOptions animations:^{
// do animations here based on object
} completion:^(BOOL finished) {
// call this recursively, but performSelector so we don't wind up the stack
[self performSelector:#selector(doAnimations) withObject:nil afterDelay:0.0];
}];
}
Perhaps post a notification in the completion routine of the 1st animation:
UIView animateWithDuration:delay:options:animations:completion:
To anser to my own question with a nice idea:
What do you think of an executionQueue or executionBlock?
I thought, I save execution Code to a block-variable, while some animation is going on. In the animation completion block, I look, whether there is a valid executionBlock.
If there is one, execute, if not, do nothing.
Also I need to save the animation state for the block to be loaded, when a async message comes in.
Related
A number of Cocoa Touch classes leverage a design pattern of coalescing events. UIViews, for example, have a method setNeedsLayout which causes layoutSubviews to be called in the very near future. This is especially useful in situations where a number of properties influence the layout. In the setter for each property you can call [self setNeedsLayout] which will ensure the layout will be updated, but will prevent many (potentially expensive) updates to the layout if multiple properties are changed at once or even if a single property were modified multiple times within one iteration of the run loop. Other expensive operations like the setNeedsDisplay and drawRect: pair of methods follow the same pattern.
What's the best way to implement pattern like this? Specifically I'd like to tie a number of dependent properties to an expensive method that needs to be called once per iteration of the run loop if a property has changed.
Possible Solutions:
Using a CADisplayLink or NSTimer you could get something working like this, but both seem more involved than necessary and I'm not sure what the performance implications of adding this to lots of objects (especially timers) would be. After all, performance is the only reason to do something like this.
I've used something like this in some cases:
- (void)debounceSelector:(SEL)sel withDelay:(CGFloat)delay {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:sel object:nil];
[self performSelector:sel withObject:nil afterDelay:delay];
}
This works great in situations where a user input should only trigger some event when a continuous action, or things like that. It seems clunky when we want to ensure there is no delay in triggering the event, instead we just want to coalesce calls within the same run loop.
NSNotificationQueue has just the thing you're looking for. See the documentation on Coalescing Notifications
Here a simple example in a UIViewController:
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(configureView:)
name:#"CoalescingNotificationName"
object:self];
[self setNeedsReload:#"viewDidLoad1"];
[self setNeedsReload:#"viewDidLoad2"];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setNeedsReload:#"viewWillAppear1"];
[self setNeedsReload:#"viewWillAppear2"];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsReload:#"viewDidAppear1"];
[self setNeedsReload:#"viewDidAppear2"];
}
- (void)setNeedsReload:(NSString *)context
{
NSNotification *notification = [NSNotification notificationWithName:#"CoalescingNotificationName"
object:self
userInfo:#{#"context":context}];
[[NSNotificationQueue defaultQueue] enqueueNotification:notification
postingStyle:NSPostASAP
coalesceMask:NSNotificationCoalescingOnName|NSNotificationCoalescingOnSender
forModes:nil];
}
- (void)configureView:(NSNotification *)notification
{
NSString *text = [NSString stringWithFormat:#"configureView called: %#", notification.userInfo];
NSLog(#"%#", text);
self.detailDescriptionLabel.text = text;
}
You can checkout the docs and play with the postingStyle to get the behavior you desired. Using NSPostASAP, in this example, will give us output:
configureView called: {
context = viewDidLoad1;
}
configureView called: {
context = viewDidAppear1;
}
meaning that back-to-back calls to setNeedsReload have been coalesced.
I've implemented something like this using custom dispatch sources. Basically, you setup a dispatch source using DISPATCH_SOURCE_TYPE_DATA_OR as such:
dispatch_source_t source = dispatch_source_create( DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, dispatch_get_main_queue() );
dispatch_source_set_event_handler( source, ^{
// UI update logic goes here
});
dispatch_resume( source );
After that, every time you want to notify that it's time to update, you call:
dispatch_source_merge_data( __source, 1 );
The event handler block is non-reentrant, so updates that occur while the event handler is running will coalesce.
This is a pattern I use a fair bit in my framework, Conche (https://github.com/djs-code/Conche). If you're looking for other examples, poke around CNCHStateMachine.m and CNCHObjectFeed.m.
This borders on "primarily opinion based", but I'll throw out my usual method of handling this:
Set a flag and then queue processing with performSelector.
In your #interface put:
#property (nonatomic, readonly) BOOL needsUpdate;
And then in your #implementation put:
-(void)setNeedsUpdate {
if(!_needsUpdate) {
_needsUpdate = true;
[self performSelector:#selector(_performUpdate) withObject:nil afterDelay:0.0];
}
}
-(void)_performUpdate {
if(_needsUpdate) {
_needsUpdate = false;
[self performUpdate];
}
}
-(void)performUpdate {
}
The double check of _needsUpdate is a little redundant, but cheap. The truly paranoid would wrap all the relevant pieces in #synchronized, but that's really only necessary if setNeedsUpdate can be invoked from threads other than the main thread. If you're going to do that you also need to make changes to setNeedsUpdate to get to the main thread before calling performSelector.
It's my understanding that calling performSelector:withObject:afterDelay: using a delay value of 0 causes the method to be called on the next pass through the event loop.
If you want your actions to be queued up until the next pass through the event loop, that should work fine.
If you want to coalesce multiple different actions and only want one "do everything that accumulated since the last pass through the event loop" call, you could add single call to performSelector:withObject:afterDelay: in your app delegate (or some other single instance object) at launch, and invoke your method again at the end of each call. You could then add an NSMutableSet of things to do, and add an entry to the set each time you trigger an action that you want to coalesce. If you created a custom action object and overrode the isEqual (and hash) methods on your action object, you could set it up so there would only ever be a single action object of each type in your set of actions. Adding the same action type multiple times in a pass through the event loop would add one and only one action of that type).
Your method might look something like this:
- (void) doCoalescedActions;
{
for (CustomActionObject *aCustomAction in setOfActions)
{
//Do whatever it takes to handle coalesced actions
}
[setOfActions removeAllObjects];
[self performSelector: #selector(doCoalescedActions)
withObject: nil
afterDelay: 0];
}
It's hard to get into details on how to do this without specific details of what you want to do.
Say there's something I want to make sure never gets called while it's being executed already.
Take for example a UIScrollView where the scrollViewDidScroll: method is called every single time any zooming occurs. What if I wanted to use [UIView animateWithDuration...] the very first time it's called, but not allow the subsequent calls to "overwrite" the already playing animation?
dispatch_once would work perfectly, if it weren't for the fact that it only allows it to run once per program execution, when I need it to run multiple times, just not at the same time overwriting one another.
How would I go about blocking subsequent calls from messing things up?
If I understand then something like this should work (since the delegate is always called from the main thread):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
static BOOL busy = NO;
if (!busy) {
busy = YES;
[UIView animateWithDuration:0.2 animations:^{
// your animations
} completion:^(BOOL finished) {
busy = NO;
}];
}
}
This code ensures the animation can only happen one at a time.
I want to call a method after an MKMapView animates to a new MKMapCamera.
I started out by attaching the MKMapCamera using this method:
[self.map setCamera:cam animated:YES];
This method causes the animation but doesn't inform me when the animation finished.
I then tried implementing a callback method by using UIView animation blocks after seeing this SO post:
MKMapCamera *cam = [[MKMapCamera alloc] init];
cam.pitch = 75;
cam.altitude = 125;
[cam setCenterCoordinate:self.location.coordinate];
[UIView animateWithDuration:3.0f animations:^{
self.map.camera = cam;
} completion:^(BOOL finished) {
[self methodToImplement];
}];
The MKMapView still animates, however the methodToImplement is called at the same time.
Thanks!
It appears that the completion handler isn't working right for that case, so you'll need to use the MKMapViewDelegate method for region did change. See WWDC 2013 - Putting Map Kit in Perspective:
Okay now that I fired off this animation to go to the next camera I need to know when that animation completes so that I can then animate to the next camera in our stack.
Well you might think of using the completion handler here but it's going to trip you up.
I know it will.
Don't use that completion handler.
Instead you need to use MKMapViews delegate method which tells you when a region change is completed.
If you're not going to use a lot of different kind of animations, then you'll might be ok with simply using the mapView:regionDidChangeAnimated: method and checking the animated flag to call your 'methodToImplement' (the animated flag will only be true for region change that was due to animation calls, and not user input like dragging the map).
In my own project I have a more complex flow, so I needed a more flexible solution, so I opted to adding an NSMutableArray of NSBlockOperation objects as a property in my view controller containing the MKMapView. Each block operation corresponding to a would be completion handler that we can't use.
In mapView:regionDidChangeAnimated: I just pop the operations one by one and execute them:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
if (animated) {
dispatch_async(dispatch_get_main_queue(), ^{
while (self.mapRegionAnimationBlocks.count > 0) {
NSBlockOperation* op = [self.mapRegionAnimationBlocks firstObject];
[self.mapRegionAnimationBlocks removeObjectAtIndex:0];
[op start];
}
});
});
and where ever I want to use a completion handler for that block, I just add to that array before calling the animation code:
dispatch_async(dispatch_get_main_queue(), ^{
NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{
[self methodToImplement];
}];
[self.mapRegionAnimationBlocks addObject:op];
});
[UIView animateWithDuration:3.0f animations:^{
self.map.camera = cam;
} completion:NULL];
Note that it's important to use NSMutableArray only from a single thread (e.g. the main thread), because it's not thread-safe.
My solution is a bit of hack, and one which should probably be wrapped in a category or subclass of MKMapView, but I haven't gotten around to that yet.
If you set your map view's delegate, you can then write a mapViewDidFinishLoadingMap:, which "Tells the delegate that the specified map view successfully loaded the needed map data."
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
// do whatever you want
}
If you want to know not only when the map data is loaded, but also when the rendering of the map is complete (in iOS7+), you can use mapViewDidFinishRenderingMap, which "Tells the delegate that the map view has finished rendering all visible tiles."
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered
{
// do whatever you want
}
For more information, see MKMapViewDelegate Protocol Reference.
A user can initiate an animation with a swipe gesture. I want to block duplicate calls to the animation, to make sure that once the animation has started, it cannot be initiated again until it has completed -- which may happen if the user accidentally swipes multiple times.
I imagine that most people achieve this control using a boolean flag (BOOL isAnimatingFlag) in the manner shown at bottom. I've done things like this before in apps many times -- but I never feel 100% certain as to whether my flag is guaranteed to have the value I intend, since the animation uses blocks and it's unclear to me what thread my animation completion block is being run on.
Is this way (of blocking duplicate animations) reliable for multi-thread execution?
/* 'atomic' doesn't
* guarantee thread safety
* I've set up my flag as follows:
* Does this look correct for the intended usage?
*/
#property (assign, nonatomic) BOOL IsAnimatingFlag;
//…
#synthesize IsAnimatingFlag
//…
-(void)startTheAnimation{
// (1) return if IsAnimatingFlag is true
if(self.IsAnimatingFlag == YES)return;
/* (2) set IsAnimatingFlag to true
* my intention is to prevent duplicate animations
* which may be caused by an unwanted double-tap
*/
self.etiIsAnimating = YES;
// (3) start a new animation
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
// (4) reset the flag to enable further animations
self.IsAnimatingFlag = NO;
}];
}
Disable the gesture if you don't want the user triggering it multiple times
- (void)startTheAnimation:(id)sender
{
[sender setEnabled:NO];
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
[sender setEnabled:YES];
}];
}
Update
Gestures also have an enabled property so you could use the same idea as if it were a button and change it' enabled state
Animation completion block will always run on the main thread.
In the example in the UIView Class Reference you can see that [view removeFromSuperview] is called directly from the block. That's mean a completion block runs on the main thread as it's the only thread safe to call UI-releated methods.
So you are all good if you calling startTheAnimation only from the main thread. If you not you need to dispatch it on the main thread anyway because you call UI-releated methods in it.
If you need to call startTheAnimation from other threads than main thread you can do something like this:
-(void)startTheAnimation{
dispatch_async(dispatch_get_main_queue(), ^{
// Your code here
});
}
Of course, it's better from user experience point of view to, for example, disable a button or modify the UI in other ways to indicate that an animation is in progress. However, it's all the same code. Whatever you need to do you first need to disable it before an animation starts and the re-enable after it's finished.
Where you call this method, you could try using dispatch_once GCD function:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[myThingy startTheAnimation];
});
I saw many threads related to this issue, but none addresses my case (I think).
My case should be simple, I have a Custom UIView in my controller, from my controller, I use the [self.myView setNeedsDisplay] and it works perfectly.
I'm having problems when Im trying to call this from inside of the UIView itself...I have a notification being sent from another class, and it is being received by my View (this works) with the information it passes, I update internal properties of this view and than I'm calling the [self setNeedsDisplay] wishing to have my screen updated with the new states, but nothing is happening, I used a NSLOG inside my drawRec method, and it is not being called at this time, it is only called when my controller class call the setNeedsDisplay, and when that happens, the update that should have happened before is showed on screen... I don't know why its not updating before...
Here are some code:
My controller asking for update: (works OK!)
- (void)addNodeToNetwork:(DTINode *)node
{
[self.myNetwork addNodeInTheNetwork:node];
self.gridView.nodesToDraw = [self.myNetwork.nodesInNetwork copy];
CGRect tempRec = CGRectMake(node.nodePosition.x, node.nodePosition.y node.nodePosition.x, node.nodePosition.y);
NSValue *rectObj = [NSValue valueWithCGRect:tempRec]; //transforma o cgrect num objeto
[self.gridView.fatherNodes setValue:rectObj forKey:node.nodeName];
[self.gridView setNeedsDisplay];
}
My notification method trying to update my drawing: (Not Working ! )
- (void) receiveTestNotification:(NSNotification *) notification
{
NSDictionary *userInfo = notification.userInfo;
DTINode *notificationNode = [userInfo objectForKey:#"myNode"];
NSLog(#"Im Here!");
for (DTINode *node in self.nodesToDraw)
{
NSLog(#"Here too");
if(node.nodeName == notificationNode.fatherNode)
{
CGRect temp = CGRectMake(notificationNode.nodePosition.x, notificationNode.nodePosition.y, node.nodePosition.x, node.nodePosition.y);
NSValue *tempObj = [NSValue valueWithCGRect:temp];
[self.fatherNodes setObject:tempObj forKey:notificationNode.nodeName];
[self setNeedsDisplay];
NSLog(#"Should REDRAW NOW!"); // It print this but no drawing is made!
}
}
}
I'm not pasting my drawRect here because it works, the problem is that is is not being called from inside my UIView setNeedsDisplay!
Anyone have any idea why this is not working????
After alot of testing, I saw something related to threads and the fact setNeedsDisplay should only be called in the mainThread...besides I never started a separeted thread in this classes, the class that raised the Notification was in a secondary thread...and aparently this was causing the issue...
to Solve it I just forced setNeedsDisplay to be called in the main thread..
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplay];
});
Swift 3:
DispatchQueue.main.async { [weak self] in
self?.setNeedsDisplay()
}
I think the correct way to use it is:
[self setNeedsDisplay:YES];
Although I always have problems to get that working :(