I am currently working on a project that I built using the alpha C4 framework.
I am trying to make start an animation as soon as the app launches without having to use an type of interaction to get it going (i.e. touchesBegan)...
But unfortunately I cant figure it out.
In C4, the way to do this is to take advantage of the following method:
-(void)performSelector:withObject:afterDelay:
And, for the current version of C4, the best way to use this is like:
#import "C4WorkSpace.h"
#interface C4WorkSpace ()
-(void)methodToRunImmediately;
#end
#implementation C4WorkSpace {
C4Shape *circle;
}
-(void)setup {
circle = [C4Shape ellipse:CGRectMake(100, 100, 100, 100)];
[self.canvas addShape:circle];
[self performSelector:#selector(methodToRunImmediately) withObject:nil afterDelay:0.1];
}
-(void)methodToRunImmediately {
circle.animationDuration = 1.0f;
circle.animationOptions = AUTOREVERSE | REPEAT;
circle.center = CGPointMake(384, 512);
}
#end
This code will start your animations after a 1/10th of a second delay... which will look immediate.
The answer above was posted a long time ago, and we were able to implement a more simple approach for this that doesn't require knowing what selectors are. The code above can now be run with the following:
-(void)runMethod:afterDelay:
Such that, in C4, the original line:
[self performSelector:#selector(methodToRunImmediately)
withObject:nil
afterDelay:0.1];
... can be rewritten as:
[self runMethod:#"methodToRunImmediately" afterDelay:0.1];
Related
I'm trying to build an animation around a UIButton. The UIButton has a UIImageView that contains an image that I'd like to shrink when the UIButton is held down and then when the UIButton is let go, I'd like to play a separate animation that does a bounce.
The issue I'm experiencing right now is that the 2nd part of the animation doesn't seem to play if I press down and then up very quickly. If I press and hold (wait for the first animation to finish), then let go, it seems to work fine.
Here's the relevant code:
-(void)pressedDown
{
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.heartPart.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
} completion:nil];
}
-(void)pressedUp
{
[UIView animateWithDuration:0.8
delay:0.0
usingSpringWithDamping:20
initialSpringVelocity:200
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.heartPart.layer.transform = CATransform3DIdentity;
}
completion:nil];
}];
}
In my ViewDidLoad I add the following:
[self.heartButton addTarget:self
action:#selector(pressedUp)
forControlEvents:UIControlEventTouchUpInside];
[self.heartButton addTarget:self
action:#selector(PressedDown)
forControlEvents:UIControlEventTouchDown];
Any idea how I can get the two animations to sequence and not interrupt each other even on a quick press?
Here's a potential plan of action. Keep two global variables, BOOL buttonPressed and NSDate *buttonPressDate. When the button is pressed, you should set buttonPressed to true and set buttonPressDate to the current date. Now in the touch up method, you would set buttonPressed to false and check whether the time interval between buttonPressDate and the current date is greater than the duration of the animation. If it is, then run the touch up animation; if not, return. In the completion block of the touch down method, you would check if the button was still pressed. If it is, then do nothing; if it's not pressed anymore, then run the touch up animation.
The effect you'll get using this approach should be the following: if you tap the button quickly, it will run the full 0.2-second shrink animation and then run the enlarge animation in sequence.
Now, if you don't want the touch down animation to run in the case of a quick touch, you should probably delay the first animation and check if the button is still pressed when you start it. If it was a quick touch, you would run a modified animation that covered both the touch down and touch up phases.
You could try out the following :-
//Set only one target with TouchUpInside.
[self.heartButton addTarget:self
action:#selector(pressedUp)
forControlEvents:UIControlEventTouchUpInside];
-(void)pressedDown
{
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.heartPart.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
}
completion:
[self pressedUp];
//Or
[self performSelector:#selector(pressedUp) withObject:self afterDelay:0.5];
];
}
-(void)pressedUp
{
[UIView animateWithDuration:0.8
delay:0.0
usingSpringWithDamping:20
initialSpringVelocity:200
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.heartPart.layer.transform = CATransform3DIdentity;
}
completion:nil];
}];
}
This way as first animation is completed then next animation will start But my concern is that it won't look appropriate and it would be a bit long animation to show to a user. Rest it's upto your app design and what all it is going to achieve with it.
I assume you are familiar with the concept of operations. If not then NSOperations allow you to keep your code modular and enable you to set the order of execution.
You could create a custom subclass of NSOperation and run animation within its execution block, then add operations with animations on queue each time user interacts with the button.
Apart from documentation from Apple, there is a great example of how to subclass NSOperation available on Github:
https://github.com/robertmryan/AFHTTPSessionOperation/blob/master/Source/AsynchronousOperation.m
Based on that "blueprint" for your custom operations, it's very trivial to achieve what you want, i.e.:
#interface PressDownAnimationOperation : AsynchronousOperation
- (instancetype)initWithView:(UIView *)view;
#end
#implementation PressDownAnimationOperation {
UIView *_view;
}
- (instancetype)initWithView:(UIView *)view {
self = [super init];
if(self) {
_view = view;
}
return self;
}
- (void)main {
// dispatch UIKit related stuff on main thread
dispatch_async(dispatch_get_main_queue(), ^{
// animate
[UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
_view.layer.transform = CATransform3DMakeScale(0.8, 0.8, 1);
} completion:^(BOOL finished){
// mark operation as finished
[self completeOperation];
}];
});
}
#end
Now in order to run animations you need to create NSOperationQueue. You can keep it within your view controller, i.e.:
#implementation ViewController {
NSOperationQueue *_animationQueue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_animationQueue = [[NSOperationQueue alloc] init];
// limit queue to run single operation at a time
_animationQueue.maxOperationCount = 1;
}
#end
Now when you want to chain animations, you simply create new operation, add it to queue and that's it, i.e.:
- (void)pressedDown {
NSOperation *animationOperation = [[PressDownAnimationOperation alloc] init];
[_animationQueue addOperation:animationOperation];
}
If user taps too fast and you notice that your operation queue is clogged with animations, you can simply cancel all animations before adding new one, i.e.:
[_animationQueue cancelAllOperations];
Custom transitions work easily with standard containers and while presenting modal view controllers. But what about using custom transitions with a totally custom container?
I'd like to use the UIViewControllerContextTransitioning protocol with my custom container and take advantage of transitioning and interactive transitions.
In the comment into the header file of UIViewControllerContextTransitioning I read:
// The UIViewControllerContextTransitioning protocol can be adopted by custom
// container controllers. It is purposely general to cover more complex
// transitions than the system currently supports.
but I can't understand how to create a Context Transitioning and start all the custom transition process.
This is very well possible!
have a look at this SO Answer.
You just have to create a viewController conforming to the UIViewControllerContextTransitioning protocol and use the ViewController Containment API (addChildViewController:, willMoveToParentViewController:, and so on).
When you want to start the custom transition, simply call the [animateTransition] Method on your transitionController. From that point on, it's the same as with the Apple-provided API.
Make your custom container fit in with the Transitioning API.
In fact, all the new protocols introduced in iOS7 (UIViewControllerContextTransitioning and so on) are not really needed to implement your own fully custom containerViewController. Its just providing a neat class-relationship and method pool. But you could write that all by yourself and would not need to use any private API.
ViewController Containment (introduced in iOS5) is all you need for this job.
I just tried gave it a try and its even working with interactive transitions.
If you need some sample code please let me know and i'll provide some :)
EDIT
By the way, this is how Facebook and Instagram for iOS is creating their navigation concept. You can scroll away the NavigationBar in these Apps and when a new ViewController is "pushed" the NavigationBar seems to have been reset during the pushing.
Ironically, i asked the exact same question a month ago here on StackOverflow :D
My Question
And I just realized i answered it myself :)
Have a nice day !
In fact, it says so but you are incomplete:
The UIViewControllerContextTransitioning protocol can be adopted by
custom container controllers. It is purposely general to cover
more complex transitions than the system currently supports. For
now, navigation push/pops and UIViewController present/dismiss
transitions can be customized.
That means you can trigger your VC's transitioningDelegate by calling presentViewController:animated:completion: method or you can implement navigationController:animationControllerForOperation:fromViewController:toViewController: in your UINavigationControllerDelegate class and return an object that conforms UIViewControllerAnimatedTransitioning protocol.
To sum up, there are only two ways to provide custom transition. Both requires presenting/pushing a VC while we add subviews to create custom container view controllers.
Probably, Apple will do what they said in the first lines of their statement in header file in the future, but for now what you want to do seems impossible.
From docs UIViewControllerContextTransitioning
Do not adopt this protocol in your own classes, nor should you
directly create objects that adopt this protocol.
Please look at MPFoldTransition, it worked for me.
You can also use the following Category, you can create your own transition using this.
#import <UIKit/UIKit.h>
#interface UIView (Animation)
- (void) moveTo:(CGPoint)destination duration:(float)secs option:(UIViewAnimationOptions)option;
- (void) downUnder:(float)secs option:(UIViewAnimationOptions)option;
- (void) addSubviewWithZoomInAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option;
- (void) removeWithZoomOutAnimation:(float)secs option:(UIViewAnimationOptions)option;
- (void) addSubviewWithFadeAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option;
- (void) removeWithSinkAnimation:(int)steps;
- (void) removeWithSinkAnimationRotateTimer:(NSTimer*) timer;
#end
Implementation :
#import "UIView+Animation.h"
#implementation UIView (Animation)
- (void) moveTo:(CGPoint)destination duration:(float)secs option:(UIViewAnimationOptions)option
{
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
self.frame = CGRectMake(destination.x,destination.y, self.frame.size.width, self.frame.size.height);
}
completion:nil];
}
- (void) downUnder:(float)secs option:(UIViewAnimationOptions)option
{
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
self.transform = CGAffineTransformRotate(self.transform, M_PI);
self.alpha = self.alpha - 0.5;
}
completion:nil];
}
- (void) addSubviewWithZoomInAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option
{
// first reduce the view to 1/100th of its original dimension
CGAffineTransform trans = CGAffineTransformScale(view.transform, 0.01, 0.01);
view.transform = trans; // do it instantly, no animation
[self addSubview:view];
// now return the view to normal dimension, animating this tranformation
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
view.transform = CGAffineTransformScale(view.transform, 100.0, 100.0);
}
completion:nil];
}
- (void) removeWithZoomOutAnimation:(float)secs option:(UIViewAnimationOptions)option
{
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
self.transform = CGAffineTransformScale(self.transform, 0.1, 0.1);
}
completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
// add with a fade-in effect
- (void) addSubviewWithFadeAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option
{
view.alpha = 0.0; // make the view transparent
[self addSubview:view]; // add it
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{view.alpha = 1.0;}
completion:nil]; // animate the return to visible
}
// remove self making it "drain" from the sink!
- (void) removeWithSinkAnimation:(int)steps
{
//NSTimer *timer;
if (steps > 0 && steps < 100) // just to avoid too much steps
self.tag = steps;
else
self.tag = 50;
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(removeWithSinkAnimationRotateTimer:) userInfo:nil repeats:YES];
}
- (void) removeWithSinkAnimationRotateTimer:(NSTimer*) timer
{
CGAffineTransform trans = CGAffineTransformRotate(CGAffineTransformScale(self.transform, 0.9, 0.9),0.314);
self.transform = trans;
self.alpha = self.alpha * 0.98;
self.tag = self.tag - 1;
if (self.tag <= 0)
{
[timer invalidate];
[self removeFromSuperview];
}
}
#end
I hope it solves your problem. enjoy.:)
I'm currently taking my first shaky steps in ios development. I'm trying to animate a free falling ball. I have a button connected to an IBAction, and and UIImageView containing an image of my ball.
Inside my action, I have a while loop that's timed using NSTimeInterval, and based on the time it takes it calculates a new position until the ball reaches 'the ground'(Yes, I realize this is probably the least optimal way to do it. But I'm having a hard time grasping the syntax (and therein my problem probably lays), the optimisation will have to come later). From what I can understand, NSTimeInterval returns the elapsed time in seconds, so even though it will increment incredibly small steps, it should work. But I may have a serious case of brain fart.
So far so good. But when I tap the button, the ball moves straight from it's starting point to it's finishing point without an animation.
-(IBAction)doshit:(id)sender{
int G = 10;
CGPoint center = [myImage center];
NSDate *startTime = [NSDate date];
NSTimeInterval T;
T = fabs([startTime timeIntervalSinceNow]);
while (center.y<480)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:T];
center.y=center.y+((G*T*T)/2);
[myImage setCenter:center];
T = fabs([startTime timeIntervalSinceNow]);
[UIView commitAnimations];
}
}
I welcome all suggestions! =)
One way to do it is to use a CADisplayLink to provide the timing -- it is tied to the display refresh rate of 1/60 of a second. If you 're using auto layout (which is on by default now), it is better to animate a constraint, rather then set a frame. So, this example uses a button at the top of the screen, whose constraint to the top of the view is connected to the IBOutlet topCon.
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (weak,nonatomic) IBOutlet NSLayoutConstraint *topCon;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(startDisplayLink) withObject:nil afterDelay:2];
}
- (void)startDisplayLink {
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)stopDisplayLink {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
static BOOL first = YES;
static double startTime = 0;
if (first) {
startTime = displayLink.timestamp;
}
first = NO;
double T = (double)displayLink.timestamp - startTime;
self.topCon.constant += ((10 * T * T)/2);
if (self.topCon.constant > 420) {
[self stopDisplayLink];
}
}
As Carl notes, you cannot perform animations by repeatedly changing things in the middle of a method call. See What is the most robust way to force a UIView to redraw? for more discussion on that.
As you may suspect, not only is this non-optimal, it actively fights iOS. iOS has many easy-to-use techniques for performing smooth animations without resorting to timers of any kind. Here is one simple approach:
- (IBAction)dropAnimate:(id)sender {
[UIView animateWithDuration:3 animations:^{
self.circleView.center = CGPointMake(100, 300);
}];
}
In the animations block, you change the thing you want to change to the final value (myImage.center in your case). UIKit will sample the current value and the final value, and figure out a path to get you there in the time you requested. For a full, runnable example with a few other features (like chained animations), see the example code from iOS:PTL Chapter 9.
The above code will use the default timing function. Once you understand that, you can move on to customizing the timing function. See Animation Pacing in the Animation Types and Timing Programming Guide. Also How to create custom easing function with Core Animation? and Parametric acceleration curves in Core Animation. But I would get your head around simple, default animations first.
I have started playing with UIProgressView in iOS5, but havent really had luck with it. I am having trouble updating view. I have set of sequential actions, after each i update progress. Problem is, progress view is not updated little by little but only after all have finished. It goes something like this:
float cnt = 0.2;
for (Photo *photo in [Photo photos]) {
[photos addObject:[photo createJSON]];
[progressBar setProgress:cnt animated:YES];
cnt += 0.2;
}
Browsing stack overflow, i have found posts like these - setProgress is no longer updating UIProgressView since iOS 5, implying in order for this to work, i need to run a separate thread.
I would like to clarify this, do i really need separate thread for UIProgressView to work properly?
Yes the entire purpose of progress view is for threading.
If you're running that loop on the main thread you're blocking the UI. If you block the UI then users can interact and the UI can't update. YOu should do all heavy lifting on the background thread and update the UI on the main Thread.
Heres a little sample
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(backgroundProcess) withObject:nil];
}
- (void)backgroundProcess
{
for (int i = 0; i < 500; i++) {
// Do Work...
// Update UI
[self performSelectorOnMainThread:#selector(setLoaderProgress:) withObject:[NSNumber numberWithFloat:i/500.0] waitUntilDone:NO];
}
}
- (void)setLoaderProgress:(NSNumber *)number
{
[progressView setProgress:number.floatValue animated:YES];
}
Define UIProgressView in .h file:
IBOutlet UIProgressView *progress1;
In .m file:
test=1.0;
progress1.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
- (void)makeMyProgressBarMoving {
NSLog(#"test %f",test);
float actual = [progress1 progress];
NSLog(#"actual %f",actual);
if (progress1.progress >1.0){
progress1.progress = 0.0;
test=0.0;
}
NSLog(#"progress1.progress %f",progress1.progress);
lbl4.text=[NSString stringWithFormat:#" %i %%",(int)((progress1.progress) * 100) ] ;
progress1.progress = test/100.00;//actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
test++;
}
I had similar problem. UIProgressView was not updating although I did setProgress and even tried setNeedsDisplay, etc.
float progress = (float)currentPlaybackTime / (float)totalPlaybackTime;
[self.progressView setProgress:progress animated:YES];
I had (int) before progress in setProgress part. If you call setProgress with int, it will not update like UISlider. One should call it with float value only from 0 to 1.
I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.