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.
Related
I am making a timer app on XCode 7 using Objective-C and I would like to disable the button that is connected to the action startCount while the following code is being executed
- (IBAction)startCount:(id)sender {
countInt = 0;
self.Label.text = [NSString stringWithFormat:#"%i", countInt];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countTimer) userInfo:nil repeats:YES];
}
What line of code would I need to add to this to disable the button connected to the action startCount?
Thanks
You need to disable the sender via the enabled flag:
((UIButton*)sender).enabled = false;
Don't forget to re-enable the button after the timer finishes.
If the enabled state is NO, the control ignores touch events [...]
Alternatively to the cast I made in the above code: Change your method signature to take in a UIButton*, not just an id, that way you can make sure the cast will not fail. A slight variation of the cast would be to cast to UIControl* instead.
If I understand you correctly: to make your code easier to understand (and to avoid unpleasant race conditions if there are multiple buttons that might be tapped/disabled), I would suggest avoiding using a timer. Instead, consider dispatch_after() like this:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
sender.userInteractionEnabled = false;
});
We get sender being passed into the method, and it will be the button that was tapped. 1 * NSEC_PER_SEC means "delay for one second."
I cannot seem to work this one out. Here is my set up:
I have a function called requestDataWithCompletion:(someBlock)block. I call it when the class is initialised. The function requests certain motion data. I want to do this periodically, therefore, the first time I call this function, I specify some completion code which sets up a timer that re-calls this function periodically. The timer calls it via another function requestDataWithoutCompletion which simply calls the requestDataWithCompletion but with an empty block (so I don't keep creating timers);
- (void) requestDataWithCompletion:(someBlock)block {
// BREAK POINT 1
[self.motionManager queryActivityStartingFromDate:start toDate:[NSDate date] toQueue:self.queue withHandler:^(NSArray *activities, NSError *error) {
// BREAK POINT 2
// do some processing;
block();
}];
}
The block simply creates a timer on the main queue, which periodically recalls this function, but with no completion (since I don't want to keep creating more timers).
block = ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
});
}
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion^{;}];
}
The amazing thing is that despite this set up, my app is creating timer after timer! I can't understand why.
I have placed break points in requestDataWithCompletion method. One is outside the block submitted to NSOperationQueue to get activity data (BREAKPOINT 1) and one is inside the block submitted to NSOperationQueue. (BREAKPOINT 2). Basically it shows that each time the method is called by the timer, BREAKPOINT 1 has an empty completion block (as it should be) but then strangely BREAKPOINT 2 has the completion block I submitted when I first called the function when initialising the class. Therefore, it continues to create a new timer each time the function is called by the timer. Over time, this means a massive number of timers and then the app crashes!
I have a feeling this is something to do with NSOperationQueue, but I really don't know what it could be.
In your initialisation (or when you first want to get the data and then continue getting it):
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
[self.timer fire]; //get the activity data immediately.
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion:^{}];
}
With your original requestDataWithCompletion: method. (though you could get rid of requestDataWithCompletion: and put it's code directly in requestDataWithoutCompletion if you're not using it elsewhere)
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 working on a project, where i have to update the text of a UILabel really regularly (0.085f). So, I insert the update of the label in a loop like this :
MetresNumber = MetresNumber + 0.25;
DisplayMetres.text = [NSString stringWithFormat:#"%07.0f", MetresNumber];
I precise that "MetresNumber" is a float, and "DisplayMetres" the UILabel.
And this sort of code really really makes bad performances ! It's incredible how slower it goes since i've added those lines.
I made some searches, and found elements like :
[DisplayMetres setNeedsDisplay];
But it didn't change and update the text on the label.
It's in a loop called with :
timer = [NSTimer scheduledTimerWithTimeInterval:0.085 target:self selector:#selector(myLoop) userInfo:nil repeats:YES];
So my question is, could my code be improve, to get better performances, or should i forget my UILabel because it's too slow with ?
Thanks !
(void)setNeedsLayout
Call this method on your application’s main thread when you want to adjust the layout of a view’s subviews. This method makes a note of the request and returns immediately. Because this method does not force an immediate update, but instead waits for the next update cycle, you can use it to invalidate the layout of multiple views before any of those views are updated. This behavior allows you to consolidate all of your layout updates to one update cycle, which is usually better for performance.
Another problem is that a scheduledTimer will not get called while the main thread is tracking touches. You need to schedule the timer in the main run loop.
So instead of doing
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
use
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Also Use Timer selector method like below:
- (void) updateLabel:(id) sender {
MetresNumber = MetresNumber + 0.25;
NSString *text = [NSString stringWithFormat:#"%07.0f", MetresNumber];
dispatch_sync(dispatch_get_main_queue(), ^{
DisplayMetres.text = text;
});
}
There shouldn't be any performance issues when you are just updating a single label every 85ms. First find out what actually causes the lags. Use the Time Profiler instrument.
I think most time will be spent on the string drawing.
Here are some tips how you can optimize your code:
You don't need to call setNeedsLayout or setNeedsDisplay explicitely on the label. Just set the text property (on the main thread of course).
The resulting string in your code will always be a 7-digit integer. Consider using an integer instead of a float. Formatting an integer will be faster.
stringWithFormat can be slow sometimes. You could try using a NSNumberFormatter or simply generate the string with: [#(MetresNumber) stringValue]
In your code the string actually doesn't change everytime the timer fires. Only every 4th time. You can set the time interval to 4*0.085 and replace MetresNumber = MetresNumber + 0.25 with MetresNumber = MetresNumber + 1.
Try using this custom UILabel class
Don't use UILabel at all. Use pre-drawn images for each digit.
Schedule the timer with NSRunLoopCommonModes (see answer from Lightygalaxy)
I'm having trouble with incrementing an int from one function by calling it in another function.
At the moment the bit I'm working on looks like this:
in the .h file I declare the int and timer as this:
int count;
NSTimer *sequenceOn;
in the .m file the segment of my function looks like this:
-(void) sequence {
count = 1;
while (count < (target)) {
sequenceOn = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(imagePlayer) userInfo:nil repeats:NO];
}
}
-(void)imagePlayer {
--CODE HERE FOR PLAYING ANIMATION--
count = count + 1;
}
All my other code is working fine and it should play through a series of images using the count value to decide which one to play. At the moment though it only plays the first animation and wont increment to the next one.
Any help would be much appreciated.
Thanks.
The timers will be fired from the run loop, which your code is blocking while running the loop. You probably want to have a timer regularly call your method:
-(void)stopTimer {
[sequenceOn invalidate];
sequenceOn = nil;
}
-(void)sequence {
[self stopTimer];
count = 1;
// start a repeating timer:
sequenceOn = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self
selector:#selector(imagePlayer) userInfo:nil repeats:YES];
}
-(void)imagePlayer {
//--CODE HERE FOR PLAYING ANIMATION--
count = count + 1;
if (count >= target) {
[self stopTimer];
}
}
I don't see anything in your while loop that increments count or decrements target, so either it's an infinite loop or it never executes, depending on the value of target.
You have the last argument of scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: set to NO, is that what you want, it will only fire once. If not you will have to give us more code to show how imagePlayer is being called repeatedly.