I am trying to get a UIView to expand using an animation block, which works perfectly. However, I want a UILabel to start at 0 and every 0.01 seconds to add 1 until it gets to 100.
I created a thread after the animation to accomplish this and it works but it causes the animation I setup to do nothing.
I have tried many different things but have had no luck. What would be the best way to accomplish this?
My simplest attempt with the same result as all the others:
[UIView animateWithDuration:1 animations:^{
_lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];
[[[NSThread alloc]initWithTarget:self selector:#selector(startcounting) object:nil]start];
-(void)startcounting{
for(int x=0; x<100; x++){
[NSThread sleepForTimeInterval:0.01];
++_mcount;
dispatch_async(dispatch_get_main_queue(), ^{
_cLabel.text = [NSString stringWithFormat:#"%i",_mcount];
});
}
}
There are two issues:
Regarding the animation of the frame while simultaneously changing the label, the issue is that changing the label's text causes the constraints to be reapplied. The correct solution is to not animate by changing the frame, but rather by changing the constraints that dictate the frame and then calling layoutIfNeeded in the animation block. See Animating an image view to slide upwards
Regarding the animating of the label:
You have no assurances that updates can be processed that quickly. (In fact you should plan on never realizing more than 60 fps.)
Even if you were able to reduce the frequency of the updates sufficiently, you are never assured that they'll be processed at a constant rate (something else could always block the main thread for a few milliseconds, yielding an inconsistent incrementing of the numbers).
So, you should instead use a timer (or better a "display link", which is like a timer, but coordinated to be called when the screen is ready to be refreshed), calculate the time elapsed (that way the counter won't be affected by other things going on, but the value will go from 0 to 100 quickly enough that it will yield the effect you were looking for), and update the label accordingly. For example:
#interface ViewController ()
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (nonatomic) CFTimeInterval startTime;
#property (weak, nonatomic) IBOutlet UILabel *label;
#end
#implementation ViewController
- (void)startDisplayLink {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
self.startTime = CACurrentMediaTime();
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
CFTimeInterval elapsed = CACurrentMediaTime() - self.startTime;
if (elapsed < 1) {
self.label.text = [NSString stringWithFormat:#"%.0f", elapsed * 100.0];
} else {
self.label.text = #"100";
[self stopDisplayLink];
}
}
Thus, combining these two points, just adjust the constraint (after adding the outlet to the constraint) and call startDisplayLink (from the main thread) and your label will be updated from 0 to 100 over the span of one second as the view animates:
[self startDisplayLink];
self.topConstraint.constant += self.lView.frame.size.height;
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
_lView.frame = CGRectMake(_lView.frame.origin.x,[[UIScreen mainScreen] bounds].size.height,_lView.frame.size.width,_lView.frame.size.height);
[[[NSThread alloc]initWithTarget:self selector:#selector(startcounting) object:nil]start];
/*
Comment this code
[UIView animateWithDuration:1 animations:^{
_lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y+_lView.frame.size.height,_lView.frame.size.width,-500);
}];
*/
Modify this method
-(void)startcounting{
int initialY = _lView.frame.origin.y;
for(int x=0; x<=100; x++){
[NSThread sleepForTimeInterval:0.01];
dispatch_async(dispatch_get_main_queue(), ^{
if (initialY>=_lView.frame.origin.y-(x*0.03))//adjust 0.03 as you required
{
_lView.frame = CGRectMake(_lView.frame.origin.x,_lView.frame.origin.y-(x*0.03),_lView.frame.size.width,_lView.frame.size.height);//adjust 0.03 as you required
}
else
{
_lView.frame = CGRectMake(_lView.frame.origin.x,initialY,_lView.frame.size.width,_lView.frame.size.height);
}
_cLabel.text = [NSString stringWithFormat:#"%i",x];
});
}
}
I finally figured it out. I should have seen this before.
First I figured it was an issue with the constraints so I removed the constraints from the UIView I am updating. This was necessary but did not fix the issue.
The entire view controller still has its own constraints and I cannot remove these as they are needed. So what I did was added a new UIView with constraints and put the UIView to be animated on this new UIView.
This fixed the problem because the animated UIView is no longer responsible for any constraints or responsible for being affected by any other constraints.
What I still do not understand is how updating the UILabel simultaneously causes the animation to fail when constraints are involved, but work perfectly fine when the UILabel code is removed. However though this did solve the issue.
Related
I have 9 custom subviews that I create and lay out programmatically. I want to animate their creation and change of position each one at a time but right now the whole set of 9 subviews is being animated at the same time.
This is my code:
- (void)viewDidLoad{
//create 9 setCardViews and add them to the array setCards to be able to identify them later
for (int i=0; i<9; i++) {
SetCardView *card = [[SetCardView alloc]initWithFrame:CGRectMake(self.gameView.center.x, self.gameView.center.y, 0, 0)];
[self.gameView addSubview:card];
[self.setCards addObject:card];
}
//layout those 9 views
[self updateLayout];
}
Then, the updateLayout method is called. It sets a frame size inside of which the subviews are laid out:
-(void)updateLayout
{
CGRect canvas = [self resizeCanvasWithNumberOfCards:[self.setCards count]];
for (SetCardView *card in self.setCards)
{
[self layOutCard:card withFrame:canvas atIndex:[self.setCards indexOfObject:card]];
}
}
Finally, inside the layOutCard method I calculate the position of the view (I omit that part of the code here) and I animate the change in its frame:
-(void)layOutCard:(SetCardView *)card withFrame:(CGRect)canvas atIndex:(NSUInteger)index
{
//calculate the new frame of the view
CGRect rect = //calculations ;
[SetCardView animateWithDuration:0.5 delay:self.delay options: UIViewAnimationOptionCurveEasyInOut animations:^{card.frame=rect;} completion:nil];
}
So all these animations happen at the same time, maybe because it's inside a loop in the updateLayout method so the controller waits for it to finish? Anyway I can't think of a way to make the animation of the views without using a loop so that they animate one at a time.
How could I do that?
Thanks
One of the way you can make it works is increase delay when you enumerate cards in updateLayout and after that pass it to layOutCard:withFrame:atIndex (of course you have to add delay parameter to that method. Or ft index parameter start from 0 and increase by 1 every time you can use it to calculate delay:
-(void)layOutCard:(SetCardView *)card withFrame:(CGRect)canvas atIndex:(NSUInteger)index
{
//calculate the new frame of the view
CGRect rect = //calculations ;
float myDelay = 0.5 * index; //0.5 is your duration.
[SetCardView animateWithDuration:0.5 delay: myDelay options: UIViewAnimationOptionCurveEasyInOut animations:^{card.frame=rect;} completion:nil];
}
You have it done (almost). One small adjustment on your code. At a run time your for loop is executed very fast leaving you with the impression that all animations are starting at the same time. You are using start with delay on your animation block already so simply increase the delay every time you go into to loop like this.
-(void)updateLayout
{
CGRect canvas = [self resizeCanvasWithNumberOfCards:[self.setCards count]];
for (SetCardView *card in self.setCards)
{
[self layOutCard:card withFrame:canvas atIndex:[self.setCards indexOfObject:card]];
// increase the delay so next animation will start after this one is finished
// check if this card is the last from your array
if([card isEqual:[self.setCards lastObject]])
{
// if card is last object set self.delay back to 0
self.delay = 0;
{
else
{
// if card is not the last object increase the delay time for next animation
self.delay += 0.5; // your current animation duration
}
}
}
I'd like to implement a custom UIDynamicBehavior that makes a view "burst". To do that I need to fade it out and scale it to 2x its size.
I do this by setting the view's alpha and bounds in the action block. However, how do I know how often the action block is called? The docs say "on each tick", but how many?
I added a counter. With no other animations, the block is called 30 times. With a few gravity and dynamic behaviors, it is called 500 times.
I also don't see how the UIDynamicAnimator knows when its behaviors are "done" moving stuff around. Can anyone shed some light on this?
The code below works sometimes, but other times the behavior stops before the view is animated completely (i.e. it is still visible).
self.action = ^{
static NSInteger count = 0;
NSLog(#"animation tick: %d", count);
count++;
UIView *view = (UIView*)[weakSelf.items lastObject];
view.alpha = view.alpha - 0.1;
CGRect bounds = view.bounds;
bounds.size.width += 1;
bounds.size.height += 1;
view.bounds = bounds;
};
For detecting when the animation has finished you could try this:
__weak Entity *weakSelf = self;
self.behavior.action = ^{
if (weakSelf.center.x == weakSelf.lastPosition.x && weakSelf.center.y == weakSelf.lastPosition.y) {
NSLog(#"end of dynamic movement");
}
weakSelf.lastPosition = weakSelf.center;
};
Or check another value that you are changing.
I'm currently testing new iOS 7 views controller transition.
What i want is a custom modal presenting transition that present your next view cut into several strip from top off screen. Each strip should appear after an incremental delay to give the desired effect.
So my code looks like this :
- (void)presentModalWithContext:(id<UIViewControllerContextTransitioning>)context
{
UIView *inView = [context containerView];
UIView *fromView = [context viewControllerForKey:UITransitionContextFromViewControllerKey].view;
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
NSTimeInterval stripTime = 1.0;
NSTimeInterval stripDelay = 1.0;
NSInteger stripCount = 10;
CGFloat stripHeight = toView.frame.size.height / stripCount;
for (NSInteger i = 0; i < stripCount; i++)
{
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[inView insertSubview:view aboveSubview:fromView];
NSTimeInterval interval = stripDelay*(stripCount-i);
[UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
NSLog(#"complete");
if (i == stripCount-1)
[context completeTransition:YES];
}];
}
}
I've already checked initial and final position of each strip and already is OK. My interval variable is also properly set at each loop.
But it seems that this is not delayed at all. All strips are moving together, giving the impression that the complete view is moving.
A quick look to basic log shows that all animations are performed at the same time :
2013-09-20 01:11:32.908 test_transition[7451:a0b] complete
2013-09-20 01:11:32.909 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.910 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.911 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.912 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete
2013-09-20 01:11:32.913 test_transition[7451:a0b] complete
Do someone is able to spot what's wrong here ?
EDIT :
It seems this is the following line that cancel the delay of any animations, even if those are not concerning the view being snapshotted :
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
If i set the parameter afterScreenUpdates to NO, the view snapshot is null and i get the following error log :
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
How do i render the view before snapshotting ? I tried [toView setNeedsDisplay] but with no success ...
Here's a solution.
Although this question is 2 years old, it's still a pertinent one as it still exists on iOS9. I realize it miiight not be as much help to the asker seeing it's been 2 years, but I only just came across this. So here's a solution.
When you want to transition between view controllers, you're probably gonna be using an Animator Object to run your custom UIView block animation code. This might be sophisticated with multiple animation blocks and some using a delay value. But if during your transition you want to capture or screenshot a portion of something (whether it's by UIView.drawViewHierarchyInRect, UIView.snapshotViewAfterScreenUpdates, or UIView.resizableSnapshotViewFromRect), using any of these methods will disengage the delays in your animation. For the last 2 methods, if you pass false for afterScreenUpdates, then it won't disengage the delays, but it also won't capture anything; it has to be true to capture something, but setting it to true will disengage your delay.
Using any of these methods will disengage the delay in your animation block, and generally mess things up. If you tell UIKit the transition is gonna be 3 secs and you have an animation block (UIView.animateWithDuration...) that has a 2 sec delay and 1 sec animation, if your delay gets disengaged then the animation runs instantly and the transition lasts just 1 sec, which throws UIKit out of sync and stuff gets generally messed up cuz it was expecting it to be 3 secs.
Here's a solution:
Say you're transition from view controller A to view controller B. In your Animator Object's code file (an object that inherits from NSObject and conforms to UIViewControllerAnimatedTransitioning protocol), you put your animation code in the animateTransition(transitionContext: UIViewControllerContextTransitioning) { ...} delegate method. If you're transitioning from VC A to VC B, say you want to screenshot something from VC B, and show it and do something with it during the transition. One way that works perfectly, is to use the drawViewHierarchyInRect method in View Controller B's viewDidLoad method, to capture and store the image (or create a UIImageView from that image and store the UIImageView) in an instance property of View Controller B. You need to use the drawViewHierarchyInRect method and not any of the other two because those require the content to be visible on screen (i.e. already rendered); the drawViewHiearchyInRect method captures offScreen content, even if it's not added to the view.
As soon as you commence a transition, the 'to view controller' gets initialized and it's viewDidLoad gets called. So in your transition animation code, you can grab the image you screenshotted (or the imageView) by referring to the view controller's property and do whatever you want with it in your transition. Your delays will not be disengaged.
Main Point: Don't screenshot stuff during the transition. Instead, put the screenshot code in the viewDidLoad of the view controller, and store its output in an instance variable and refer to that in your transition.
Hope this helps, I only just came across this problem today and just came across a solution.
After working on your code for a bit, and comparing it to mine, where the delay parameter was honored correctly, I still can't figure out why yours doesn't work. In any case, I found another way that does work. I break the animation into two parts. In the first part, I create the slices of the view using your code, add them to the inView, and also to a mutable array. In the second part, I call the animation block recursively, with no delay, until the last strip is displayed. One limitation to this approach, is that each strip animation has to complete before the next one begins (since the next one is called from the completion block), so you don't have independent control over the duration and delay. Anyway, here is what I did. In the presenting view controller, I just do this:
-(IBAction)presntBlue:(id)sender {
BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:#"Blue"];
blue.modalTransitionStyle = UIModalPresentationCustom;
blue.transitioningDelegate = self;
[self presentViewController:blue animated:YES completion:nil];
}
-(id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
RDPresentationAnimator *animator = [RDPresentationAnimator new];
animator.isPresenting = YES;
return animator;
}
And in the RDPresentationAnimator class, I have this:
#interface RDPresentationAnimator () {
NSInteger stripCount;
CGFloat stripHeight;
NSMutableArray *stripArray;
}
#end
#implementation RDPresentationAnimator
#define ANIMATION_TIME .3
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext {
return ANIMATION_TIME;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)context {
UIView *inView = [context containerView];
UIView *toView = [context viewControllerForKey:UITransitionContextToViewControllerKey].view;
stripCount = 10;
stripHeight = toView.frame.size.height / stripCount;
stripArray = [NSMutableArray new];
for (NSInteger i = 0; i < stripCount; i++)
{
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, toView.frame.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[inView addSubview:view];
[stripArray addObject:view];
}
[self animateStrip:stripCount - 1 withContext:context];
}
-(void)animateStrip:(NSInteger) index withContext:(id <UIViewControllerContextTransitioning>) context{
[UIView animateWithDuration:ANIMATION_TIME animations:^{
UIView *view = stripArray[index];
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
if (index >0) {
[self animateStrip:index - 1 withContext:context];
}else{
[context completeTransition:YES];
};
}];
}
I thought I'd add another answer that does give you the independent control over the stripTime and stripDelay. I never did find a way to make it work using the new UIViewControllerContextTransitioning methods. This way uses normal UIView animations, followed by a no animation presentViewController. This method should work correctly in either portrait or landscape orientation (notice that I use self.view.bounds to calculate stripHeight and snapRect, so that those values will be correct for either orientation).
#interface ViewController () {
NSInteger stripCount;
CGFloat stripHeight;
NSMutableArray *stripArray;
}
#end
#implementation ViewController
-(IBAction)presntBlue:(id)sender {
BlueViewController *blue = [self.storyboard instantiateViewControllerWithIdentifier:#"Blue"];
[self animateView:blue];
}
-(void)animateView:(UIViewController *) toVC; {
UIView *toView = toVC.view;
toView.frame = self.view.bounds;
[self.view addSubview:toView];
NSTimeInterval stripDelay = 0.2;
NSTimeInterval stripTime = 1.0;
stripCount = 10;
stripHeight = self.view.bounds.size.height / stripCount;
stripArray = [NSMutableArray new];
for (NSInteger i = 0; i < stripCount; i++) {
CGFloat offsetY = i*stripHeight;
CGRect snapRect = CGRectMake(0, offsetY, self.view.bounds.size.width, stripHeight);
UIView *view = [toView resizableSnapshotViewFromRect:snapRect afterScreenUpdates:YES withCapInsets:UIEdgeInsetsZero];
CGRect stripRect = CGRectMake(0, -(stripCount-i)*stripHeight, snapRect.size.width, snapRect.size.height);
view.frame = stripRect;
[self.view addSubview:view];
[stripArray addObject:view];
}
[toView removeFromSuperview];
for (NSInteger i = 0; i < stripCount; i++) {
NSTimeInterval interval = stripDelay*(stripCount-i);
UIView *view = stripArray[i];
[UIView animateWithDuration:stripTime delay:interval options:0 animations:^{
CGPoint center = view.center;
center.y += stripCount*stripHeight;
view.center = center;
} completion:^(BOOL finished) {
if (i == 0){
[self presentViewController:toVC animated:NO completion:nil];
}
}];
}
}
Added note:
In the animateView: method, I add the toView to self.view,, and then remove it after making the strips. I do this to make sure it works correctly in portrait and landscape -- if I omit those two statements, there's a slight glitch in the landscape animation when the animation finishes. If I have those two lines in, I occasionally get a glitch at the beginning where you can see the whole toView for a brief flash. I don't know why this only happens occasionally, and I haven't updated my phone yet, so I don't know if this happens on the device as well.
I am trying to create a Calibration View! I have 12 calibration points as UIImageViews. Now I want to show every point for 5 seconds. So the whole calibration time is 1 minute. The Alpha of the ImageViews is set to 0.0! In the UI Animation I want to set the Alpha to 1.0 for one point after an other, for each point only for 5 seconds.
So far I did this (see the code)! But this animates me (fades in) after 5 seconds for 5 seconds all 12 Points at once!How can I solve this for one after another with a NSTimer and UI Animation? Thanks
-(id)init{
_calibrationViewArray = [[NSMutableArray alloc]init];
_calibrationPoint1 = [[UIImageView alloc]initWithFrame:(CGRectMake(115.5,113.0,25.0,25.0))];
_calibrationPoint1.backgroundColor = [UIColor redColor];
_calibrationPoint1.alpha = 0.0;
[self addSubview:_calibrationPoint1];
[_calibrationViewArray addObject:_calibrationPoint1];
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(onTimer) userInfo:nil repeats:YES];
}
-(void)onTimer{
for (int i = 0; i < [_calibrationViewArray count]; i++) {
UIImageView* currentCalibrationPoint = [_calibrationViewArray objectAtIndex:i];
[UIView beginAnimations:#"Calibration" context:nil];
[UIView setAnimationDuration:5.0];
// Make the animatable changes.
currentCalibrationPoint.alpha = 1.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
}
}
Declare a variable in your class to be:
int cont;
Initialize variable when is needed:
cont=0;
Change your code to this:
-(void)onTimer{
//Loop is not needed, with the timer and counter is enough
UIImageView* currentCalibrationPoint = [_calibrationViewArray objectAtIndex:cont];
[UIView beginAnimations:#"Calibration" context:nil];
[UIView setAnimationDuration:5.0];
// Make the animatable changes.
currentCalibrationPoint.alpha = 1.0;
// Commit the changes and perform the animation.
[UIView commitAnimations];
if(cont>0){
//Hide previous view (here you can make another animation, instead of changing alpha right away)
[_calibrationViewArray objectAtIndex:cont-1].alpha = 0.0;
}
cont++;//+1
}
In your onTimer method you shouldn't be looping over all of the image views, you should have a currentIndex or similar variable so you can just get the next image view and run an animation on it alone.
Do not use timer at all.
Use the:
[UIView animateWithDuration: animations: completion:]
method.
In the animation block fade one of the dots, then in completion block run the animation again.
I have one UIImageView and number of UIImageView which are entering in screen after some time interval. I want to check if that one ImageView is collided with any others.
Please help me.
The general process for detecting collisions between rectangular shaped views is to use CGRectIntersectsRect() to see if the frames of two views intersect. So, if you have a NSMutableArray of UIImageView objects, you can perform a fast enumeration through them and look for the collision, something like:
for (UIView* view in self.imageViewsArray)
{
if (CGRectIntersectsRect(view.frame, viewToDetectCollisionWith.frame))
{
// do whatever you want when you detect the collision
}
}
Or, you can use the enumerateObjectsUsingBlock which uses fast enumeration, but gives you both the numeric index, idx, and the individual UIView objects in the array in a single statement:
[self.imageViewsArray enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
if (CGRectIntersectsRect(view.frame, viewToDetectCollisionWith.frame))
{
// do whatever you want when you detect the collision
}
}];
Original answer:
If you're animating the UIImageView objects via the various automated animation techniques, you have to use something like CADisplayLink to check for collisions because iOS is taking care of the animation and you otherwise are not informed of the frame of the various views in the middle of an animation. The CADisplayLink informs your app every time the animation has progressed, so you get information about the location of views as the animation progresses. Sounds like you're not availing yourself of built in animation techniques, but rather using a NSTimer to manually adjust frames, so you might not need the below code. But if you ever pursue a more automated animation, you can use the following technique.
What you can do is use a CADisplayLink to get information about the screen while the animation is in progress:
#import <QuartzCore/QuartzCore.h>
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayLinkHandler)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
You might even want to store that in a class property so you can add it and remove it as the view appears and disappears:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayLinkHandler)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
self.displayLink = nil;
}
Then, you can start your animation. I'm just using the standard block-based animation to continually animate the changing of two image view frames, but you'll obviously do whatever is appropriate for your app:
[UIView animateWithDuration:4.0
delay:0.5
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
self.imageView1.frame = ... // I set the frame here
self.imageView2.frame = ... // I set the frame here
}
completion:nil];
Now I can detect when these two frames collide (i.e., whether their frames intersect) with this CADisplayLink handler which grabs the relevant presentationLayer properties to get the "in progress" frame coordinates:
- (void)displayLinkHandler
{
id presentationLayer1 = self.imageView1.layer.presentationLayer;
id presentationLayer2 = self.imageView2.layer.presentationLayer;
BOOL nowIntersecting = CGRectIntersectsRect([presentationLayer1 frame], [presentationLayer2 frame]);
// I'll keep track of whether the two views were intersected in a class property,
// and therefore only display a message if the intersected state changes.
if (nowIntersecting != self.wasIntersected)
{
if (nowIntersecting)
NSLog(#"imageviews now intersecting");
else
NSLog(#"imageviews no longer intersecting");
self.wasIntersected = nowIntersecting;
}
}
By the way, you may need to add Quartz 2D, the QuartzCore.framework, to your project. See the Project Editor Help.