Hi I would like to know what's is wrong with this codes :
IBOutlet UIImageView sam1;
IBOutlet UIImageView sam1;
NSTimer *timer1;
NSTimer *timer2;
-(void)start {
[self performSelector:#selector(myTimer1) withObj....
}
-(void)startAlso {
[self performSelector:#selector(myTimer2) withObj....
}
-(void)myTimer1 {
timer1 = [NSTimer scheduledTimeWithInterval .....
}
-(void)myTimer2 {
timer2 = [NSTimer scheduledTimeWithInterval .....
}
-(void)specialFuct {
[timer1 invalidate];
[timer2 invalidate];
}
Desc of my program is a simple game using imageview.
each imageview has their own NSTimer since they are both moving.
when 2 imageview collides, their timer is stop using invalidate.
my problem is, 2 imageview is coming from center.y -40 which is
outside the screen layout.
when the 2 imageview is inside the screen, it stops.
but when it is still outside the screen, -40 let say.
their NSTimer is still moving which is causing me a bug.
is their any explanation of why is this happening?
thank you in advance! first time xcoder here.
UPDATE!
I already found the problem,
it is on the
[self performSelector:#selector(myTimer1) withObj....
part.
What's happening is, the NSTimer is already shutdown but the performSelector delay is still runnning. My alternative way is to have a boolean and check if the game is already over or not.
if(gameOverIsNotOver) {
[self performSelector:#selector(myTimer1) withObject:nil afterDelay:3];
}
is there any best way to do this? thanks in advance!
You have awful description with dots in code. But hope my answer will help and solve your problem.
If you create scheduled NSTimer you can call invalidate to cancel its action.
For performSelector you can call:
[NSObject cancelPreviousPerformRequestsWithTarget:self];
And it will cancel all future selectors with delay for this (self) class.
Better don't use selectors and timers together. Global variables - is a bad pattern too.
Related
What's the best way to execute a series of commands in a certain order? I keep getting frustrated by the darn concurrency of execution in ObjC. This is one of these cases where I realize that I'm a designer, not a "real" coder.
I'm experimenting with SpriteKit on iOS, and I want a sequence of things to occur when an energy gauge reaches <=0.
Call a method to create an explosion at the final contact. The method takes arguments about position and size of explosion.
Call another method afterwards that calls up a new scene, a results screen.
My problem occurs when the new scene gets called before I get a chance to see the last explosion.
Here's the relevant code:
- (void) doGameOver
{
damageIndicator.progress = 0;
energyLeft.text = #"Energy:0%";
GameOver *newScene = [[GameOver alloc]initWithSize:self.size];
newScene.timeElapsed = [started timeIntervalSinceNow];
[self.view presentScene:newScene transition:[SKTransition fadeWithColor:[SKColor whiteColor] duration:1]];
[damageIndicator removeFromSuperview];
}
- (void) makeExplosionWithSize:(float)myBoomSize inPosition:(CGPoint)boomPosition
{
NSString *myFile = [[NSBundle mainBundle] pathForResource:#"explosion" ofType:#"sks"];
SKEmitterNode *boom = [NSKeyedUnarchiver unarchiveObjectWithFile:myFile];
boom.position = boomPosition;
boom.particleSize = CGSizeMake(myBoomSize, myBoomSize);
[self addChild:boom];
[self runAction:self.playMySound];
}
- (void)adjustScoreWithDamage:(float)hitDamage atPosition:(CGPoint)pos
{
_damage = _damage -(hitDamage);
if (_damage < 0) {
//these are the two things I need to execute sequentially
[self makeExplosionWithSize:500 inPosition:pos];
[self doGameOver]
}
}
I've tried schemes using bools (gameOver = YES), but think I may need to create a completion handler, which just makes my head spin.
Can anyone suggest the easiest way to accomplish this?
Thank you in advance.
Easiest (not best) probably would be to replace
[self doGameOver];
with
[self performSelector:#selector(doGameOver) withObject:nil afterDelay:2.0];
I may misunderstand what you're going for, but it sounds like what should happen is:
The explosion begins.
There's a pause of [n] seconds.
The Game Over screen is presented.
To accomplish that, you might want to just fire "doGameOver" with an NSTimer rather than worry about having it fire immediately after the explosion completes.
Here's an example with a 3 second delay:
NSTimer *gameOverTimer = [NSTimer timerWithTimeInterval:3.0 target:self selector:#selector(doGameOver:) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:gameOverTimer forMode:NSDefaultRunLoopMode];
I'm running mathematical computation in a background thread. Attempting to post results in real time in a UITextView. However the results don't show up until the background thread completes. Why not?
I kick off a method in the background,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[self v2];
});
The background thread method is of the form,
- (void) v2 {
NSString *result;
// ... loop a bunch of times generating lots of results
for (bunch of stuff to compute) {
// If using dispatch_async, nothing is displayed until this method finishes
// If dispatch_sync then it does display and update
result = [self computeNextValue];
dispatch_async(dispatch_get_main_queue(), ^() {
textView.text = result;
});
} // end computation
}
This actually hadn't been much of a problem until I started trying to scroll the view. Painfully slow. So I created a NSTimer to periodically scroll the UITextView. However, even though the timer popped and the method is run to request the scroll, the UITextView doesn't scroll until the background method completes.
First, make sure that you are using weakSelf rather than self within the block.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[weakSelf v2];
});
...
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^() {
weakSelf.textView.text = result;
});
But that won't cause the delayed update. I am very suspect of the queue's priority, DISPATCH_QUEUE_PRIORITY_LOW. Try using DISPATCH_QUEUE_PRIORITY_DEFAULT instead.
I think I figured it out, at least empirically.
It looks like setting text on a UITextView or programmatically initiating scrolling sets up an animation sequence thats run in another thread. What seems to be happening is that the requests to set the text of the UITextView is coming in faster than the ability of UITextView to setup the animation. When the property changes again, it apparently cancels the earlier animation that it was setting up on another thread. As I continue to flood it with change requests, its never able to finish what it wants to do before the value of 'text' has changed again.
My solution involves multiple steps.
In my original approach I was setting textView.text very rapidly to some new value. I set up a timer to periodically request the UITextView to scroll.
In my modification, I calculate the new value of the result string, but do not set it to the UITextView. Instead the text is set on the textview periodically based on the timer. This allows the text view to catch up.
However, I noticed that this still wasn't reliable. If I happened to set the text again while it was still scrolling, weird effects would occur, such as a very slow scroll. It seems that the scroll animation and repeated settings of text were still causing a problem.
So solve this problem, I created a property to indicate if the view is scrolling. Set the view controller as the UITextField delegate. When I request the view to scroll, I set the flag to indicate its scrolling. Only update the content and request scroll if its not already scrolling. Ends up working great. Doesn't matter how fast I set the timer, it ends up waiting appropriately.
// ViewController.h
#property BOOL isViewScrolling;
// ViewController.m
// initialize property in viewDidLoad
- (void)viewDidLoad {
self.isViewScrolling = FALSE;
textView.delegate = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(scrollIt) userInfo:nil repeats:TRUE];
}
- (void)scrollIt {
NSLog(#"scrollit thread=%d", [[NSThread currentThread]isMainThread]);
if (!self.isViewScrolling) {
textView.text = self.result;
NSRange range = NSMakeRange(textView.text.length - 1, 1);
self.isViewScrolling = TRUE;
[textView scrollRangeToVisible:range];
}
}
// UITextView delegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
NSLog(#"Animation stopped");
self.isViewScrolling = FALSE;
}
Try changing the predefined dispatch queue string to end with background.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^() {
weakSelf.textView.text = result;
});
Also you should add a UIActivityIndicator and start animating at the beginning of the operation, then stop animating after the textview.text field has been updated.
It's a nice feature to have to show the user there is a process currently being done
Also I would stay away from NSThread as I have read a few forums and docs from apple that emphasize the use of GCD and block operations.
I know that these NSTimer questions have come up numerous times, however since none seem to involve executing blocks that change the UI, I figured this is still an original question.
I have a subclass of UIButton that, for convenience sake (me, coming from an Android background), has an onClick and onHoldClick function. onClick simply takes a block and executes it in the selector that responds to UIControlEventTouchUpInside. The click function works great. For example:
[myButton setOnClick:^{
NSLog(#"clicked");
}];
The hold click functionality is not working so well.
[myButton setOnHoldClick:^{
NSLog(#"still holding click...");
}];
This listens for the UIControlEventTouchDown event, and performs a task after a delay:
- (void)clickDown:(id)sender
{
isClicked = YES;
[self performSelector:#selector(holdLoop:) withObject:nil afterDelay:delay];//For the sake of the example, delay is set to 0.5
}
The hold loop runs a repeated timer on another function, which handles the block execution (the timer variable is an NSTimer declared in the header file):
-(void)holdLoop:(id)sender
{
[self cancelTimers];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(death:) userInfo:nil repeats:YES];
}
-(void)death:(id)_delay
{
if (isClicked)
{
_holdBlock();
}
else
{
[self cancelTimers];
}
}
The block that executes changes the value of a float, which is used to update the value of a label, which is then redrawn.
The first time the hold click event occurs, this works great. After that, it seems like timers don't get canceled, and new timers are still added. This is what my cancelTimers function looks like (calls here are retrieved from a collection of the other questions on this topic):
-(void)cancelTimers
{
[_timer invalidate];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(death:) object:nil];
}
What am I doing wrong, and how do I fix it?
Edit
I do, in fact, already have the function that responds to touch up inside:
- (void)clickUp:(id)sender
{
isClicked = NO;
[self cancelTimers];
_clickBlock();
}
Furthermore, I have realized that the issue comes from an unhandled cancel event. Is there a reason why iOS would auto-cancel my long press?
Solved
Since the block redrew the UI, it was also redrawing the buttons (and resetting their functionality). This event was causing a cancel event to be called on the button - which was not handled. Adding the following:
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchCancel];
[self addTarget:self action:#selector(cancelClick:) forControlEvents:UIControlEventTouchUpOutside];
-(void)cancelClick:(id)sender
{
isClicked = NO;
[self cancelTimers];
}
As well as reconsidering what changes are made in the block, has gotten me past this issue.
As I understood from the comments and the code, the clickDown: is called for UIControlEventTouchDown so isClicked is set to YES when the first time the button is touched down. You need to add a selector to the event UIControlEventTouchUpInside. It's called when the user lifts his finger while being iside the bound of the button. Inside that method, set isClicked to NO.
I don't want to create NSTimer object. How do I invalidate timer? I want to invalidate timer in viewWillDisappear.
-(void) viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
A
you have to hold on to the timer you create:
#interface MONObject ()
#property (nonatomic, retain) NSTimer * timerIvar;
#end
#implementation MONObject
...
- (void)viewDidLoad
{
[super viewDidLoad];
self.timerIvar = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
- (void)invalidateTimer
{
[self.timerIvar invalidate];
self.timerIvar = nil;
}
- (void)viewWillDisappear:(BOOL)animated
{
...
[self invalidateTimer];
}
B
another option would be to invalidate the timer that is passed in the callback, but that won't occur within viewDidUnload:. therefore, it doesn't quite apply in this scenario:
- (void)onTimer:(NSTimer *)pTimer
{
[pTimer invalidate];
}
If you want to be able to cancel the timer, you have to refer to the timer you’re cancelling, and that means you have to keep the pointer to the timer around, see justin’s answer.
Keeping a reference to the timer is the right way to do it, but for the sake of completeness you may also use the -performSelector:withObject:afterDelay: method as a poor man’s timer. That call may be invalidated using +cancelPreviousPerformRequestsWithTarget:. Sample code:
- (void) viewDidLoad
{
[super viewDidLoad];
[self performSelector:#selector(timerTick) withObject:nil afterDelay:10];
}
And then:
- (void) viewWillDisappear
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[super viewWillDisappear];
}
But this is not the right way to do it, because there might be other perform-selector requests pending on your object that you would cancel. It’s best to keep your timer around, that way you know exactly what you’re cancelling.
By the way, it’s also probably a bad idea to run a timer in -viewDidLoad. View loading may happen anytime, without any relation to view being displayed.
Maybe this method can help you:
[self performSelector:#selector(onTimer:) withObject:nil afterDelay:10];
If you don't want to hold on to your timer, the NSTimer object will be passed to the timer method (in your case onTimer:), so in that method you could check whether the timer is still needed and invalidate it. However, you will run into trouble if the view comes back before you invalidated the timer, and you create a new one.
By far the best way is to store the timer into an instance variable. It works, no clever tricks, and you'll know six months later what you did. I'd probably write a
#property (readwrite, nonatomic) BOOL hasTimer;
getter returns YES iff the timer is not nil, setter invalidates the timer or creates a new one.
I have a view-based template app and have UILabel & UIButton. For debugging purposes I'm showing and hiding the button whilst changing the UILabel.text.
In C++ I would 'thread root();' to execute the root method but I don't know how to in Objective-c. How to run my 'root' method once the view loads?
-(void) root
{
[bombButton1 setHidden:NO];
int s = 0;
int j = 10;
while ( s < j )
{
[bombButton1 setHidden:YES];
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
s++;
}
Edit:
Right, now I have: (but I get ERROR: Expected method body on the "-(void) rootMethod: NSTimer * timer {" line)
-(void) applicationDidFinishLaunching : (UIApplication *) application {
spawnTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(rootMethod:) userInfo:nil repeats: YES];
}
-(void) rootMethod: NSTimer * spawnTimer {
int s = 0;
int j = 10;
while ( s < j )
{
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
//debugLabel.text=#"debug test complete";
s++;
}
}
Several ways to do this, I think. Here's one:
[self performSelectorInBackground:#selector(root) withObject:nil];
You'd make this call in say, your -(void)viewDidAppear: method.
You may run into issues running code on threads other than the main thread that tries to manipulate the UI.
That sleep(1) is worrisome. You could use a repeating NSTimer instead and eliminate the sleep(1) entirely. Something like:
[NSTimer scheduledTimerWithInterval:2.0 target:self
selector:#selector(root:) userInfo:nil repeats:YES];
For NSTimer, you'd have to change your method, root, to have a signature like
- (void)root:(NSTimer*)theTimer
You need to implement a called viewDidLoad.
- (void) viewDidLoad() {
// your code here
}
I'm sure you have your reasons, but you really shouldn't iteract with UI components in anything other than the UI thread. What you actually need to do is use an NSTimer to call a method on the UI thread multiple times.
What you should be doing is performSelectorOnMainThread when you want to update the UI Thread.
Do your running in the background and update variables that will contain the updated values, then use performSelectorOnMainThread on the View, sending it to a method that will merely update the Textbox with the data in the variables.
You can do anything in a background thread, except update the ui.
Edit: Furthermore I dont recommend using Timers in place of background threads, I have had instances when using Timers, where only so many get created and when I expected a background thread to fire, it never did. The timer actually never fired, even tho it was created.