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.
Related
Hi everyone,
small question.
I have an UIView class that I created, AnimatedUIView.
In the init, I create a subview that I animate like this:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[self animateView];
}
];
}
];
}
I have to call [self animateView] myself in the second completion block rather than using option repeat because I want to delay 1 second only the first animation.
Everything works fine, I alloc and init my view and add it as a subview, and it is animated as should.
But, when I close my superview, I guess ARC does its work and deallocs the animatedView, but my CPU goes 100 % !
I have investigated and if I comment the call to [self animateView], my CPU doesn't skyrocket when I close the superview.
So I've managed to solve the problem (by putting a condition before the call and changing a boolean value before closing the superview, keepAnimating = NO) , I just wanted to understand WHY it does this?
Why does it try to keep animating, and why does it use so much CPU?
If I put an NSLog in the completion block, I first see it every 8 seconds, but when I close the superview, the NSLog just keeps appearing every ms...
BTW: it relates to this question : UIView animation using 90%CPU , which was not really answered. Thanks a lot!
CPU going to 100% almost always means infinite recursion.
We can only guess because only Apple knows what's inside the animation source code.
In this case I guess this could be caused by triggering the animation when the animation cannot run anymore (e.g. there is no layer since we are in dealloc state). That would mean the completion handler is called immediately and that leads to infinite recursion.
There are safer ways to create infinite animations (e.g. using CAAnimation on the layer).
You should not use self in side the block. It will create a retain cycle. If it is necessary to call self create a weak self pointer. Code like this
__weak typeof(self) weakself = self;
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
[weakself animateView];
}
];
}
];
But main issue with your code is you calling animateView recursively without any base case. So i consuming CPU cycles... do not use animateView without base case.
Try using timer
Write following line in your viewDidLoad or any where else from where you want animation to start.
[NSTimer scheduledTimerWithTimeInterval:9 target:self selector:#selector(animateView) userInfo:nil repeats:YES];
// 9 is total animation duration.....
update your animateView method as follows:
- (void)animateView{
[UIView animateWithDuration:4.0
delay:1.0
options: nil
animations:^{
// Animation PART 1
}
completion:^(BOOL completed){
[UIView animateWithDuration:4.0
animations:^{
// Animation PART 2
}
completion:^(BOOL completed){
}
];
}
];
}
Not sure how to describe this, but what would be the easiest way to have a UILabel display Loading, then Loading., then Loading.., then Loading..., and then repeat?
I have been looking into timers but it all seems a bit excessive. Anyone know of a cool and quick trick to pull something like that off? Thanks!
Not sure about your specific use case (more info please or code?) but have you tried animation blocks? Something like:
- (void)animate
{
__block UIView * blockSelf = self;
[UIView animateWithDuration:0.1f animations:^{
if ([blockSelf.label.text isEqualToString:#"Loading..."]) {
blockSelf.label.text = #"Loading";
} else {
blockSelf.label.text = [NSString stringWithFormat:#"%#.", blockSelf.label.text];
}
} completion:^(BOOL finished) {
if (blockSelf.processIsFinished) {
[self moveOn];
} else {
[blockSelf animate];
}
}];
}
Alternatively, something like MBProgressHUD might be useful depending on what type of process is "loading".
I'm trying to make multiple buttons fall down the screen all at once, at different speeds. But when I have my if statement check if it passed through the value, all the other buttons disappear with it. I'm also incrementing the score to go -1 each time a button passes through the Y value. Any help is appreciated, thanks.
- (void)b1Fall
{
b1.center = CGPointMake(b1.center.x, b1.center.y+6);
if (b1.center.y >= 506) {
[self updateScore];
b1.center = CGPointMake(44, 11);
}
}
- (void)b2Fall
{
b2.center = CGPointMake(b2.center.x, b2.center.y+7);
if (b2.center.y >= 506) {
[self updateScore];
b2.center = CGPointMake(160, 11);
}
}
- (void)b3Fall
{
b3.center = CGPointMake(b3.center.x, b3.center.y+8);
if (b3.center.y >= 506) {
[self updateScore];
b3.center = CGPointMake(276, 11);
}
}
- (void)updateScore
{
healthLabel.text = [NSString stringWithFormat:#"%d", [healthLabel.text intValue]-1];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// REMOVE AFTER TESTING
b1Timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(b1Fall) userInfo:nil repeats:true];
b2Timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(b2Fall) userInfo:nil repeats:true];
b2Timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(b3Fall) userInfo:nil repeats:true];
}
A few things:
1.) Put these animations in viewDidAppear instead of viewDidLoad - you want the animation to begin when the user is actually looking at the buttons, not when the view is loaded into memory, where the user may not be able to see it (viewDidLoad).
2.) Don't use NSTimer for animations, use this:
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
}
completion:^(BOOL finished){
// loop your animation here.
}];
3.) If you want to make a game, I don't recommend using this approach. All animations using UIKit are going to perform on the main thread and block the user flow. What you want is a framework like Cocos2D where the animations are doing in the GPU and ASYNC game logic is readily supported. UIKit isn't a good choice for game development in general.
I am using animateWithDuration to change the value of a UISlider:
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [myUISlider setValue:10]; }
completion:^(BOOL finished){ }];
The problem is, I need to be able to display the current value of the UISlider while it is changing. Is this even possible using animateWithDuration? I tried creating a UISlider subclass and overriding setValue in hopes of getting access to the slider's value as it is being changed:
-(void)setValue:(float)newValue
{
[super setValue:newValue];
NSLog(#"The new value is: %f",newValue);
}
But that code only gets called at the very end of the animation block. In a way that makes perfect sense, since I really only called setValue once. But I was hoping that the animation block would somehow call it over and over again within its internal mechanisms, but that appears not to be the case.
If UIView animateWithDuration is a dead-end here, I wonder if there is a better way to acheive this same functionality with something else? Perhaps another slick little block-driven part of the SDK I don't know about which allows animating more than just UIView parameters?
I think the best way is to handle it is to code a custom Slide you can creat it like progress bar which there are many demo on github.
You can also use NSTimer to do it , but I do not think it is a very better way.
When you tap , you can creat a timer :
_timer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(setValueAnimation) userInfo:nil repeats:YES];
and set a ivar: _value = oldValue;
in the setValueAnimation method :
- (void)setValueAnimation
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
return;
}
_value += .05;
[self setVale:_value];
}
UPDATE
how to add block:
1st:you can define a block handler :
typedef void (^CompleteHandler)();
2nd: creat your block and added it into the userInfo :
CompleteHandler block = ^(){
NSLog(#"Complete");
};
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:block,#"block", nil];
3rd: make the NSTimer:
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(setValueAnimation:) userInfo:userInfo repeats:YES];
4th:achieve your timer method:
- (void)setValueAnimation:(NSTimer *)timer
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
// also can use [_delegate complete];
CompleteHandler block = [timer.userInfo objectForKey:#"block"];
if (block) {
block();
}
return;
}
_value += .05;
[self setVale:_value];
}
I do not know if there is any sort of notification that you could "tap into" to get the UIView animation "steps", but you can do the animations "manually" using an NSTimer, and that will allow you to continuously update your value.
If there's an out of the box solution using UIView - I'm all ears. This animation framework (only 2 classes!) - PRTween https://github.com/chris838/PRTween will give you access to convenience timing functions and also the changed value.
Here's an updated project of mine https://github.com/jdp-global/KACircleProgressView/
PRTweenPeriod *period = [PRTweenPeriod periodWithStartValue:0 endValue:10.0 duration:1.0];
PRTweenOperation *operation = [PRTweenOperation new];
operation.period = period;
operation.target = self;
operation.timingFunction = &PRTweenTimingFunctionLinear;
operation.updateSelector = #selector(update:)
[[PRTween sharedInstance] addTweenOperation:operation]
- (void)update:(PRTweenPeriod*)period {
[myUISlider setValue:period.tweenedValue];
}
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.