I need some help with xcode...
I need to show a value on label with [label1 setIntValue: someInt] wait some secs and do again [label1 setIntValue: otherInt]. I tried with sleep() but the ui stucks and only the second int is shown. What I need to do? Thank you very much!
What you need to do is to set up a timer. After setting the first value on the label, use this:
NSTimer* t = [NSTimer scheduledTimerWithTimeInterval:1 target: self selector:#selector(timerEnded) userInfo: nil repeats:NO];
In this case you're waiting 1 second before triggering timerEnded. So, after this, create the timerEnded method. This is the method that gets called after 1 second.
-(void)timerEnded{
//set value to label
}
Related
I created a simple maths based app. The app asks a user 6 multiplication table questions. e.g. Q1 10 x 10
The user enters an answer
The app displays whether the user's answer was correct or incorrect and displays this using the following
IBOutlet NSTextField *CorrectIncorrect;
In a cycle of the app (1 question of 6) CorrectIncorrect is used to display the strings 'correct' or 'incorrect' using this line of code
[CorrectIncorrect setStringValue:receivedAnswer];
[[CorrectIncorrect window] display];
Then as the next question is posed to the user, the either string is cleared using the following code.
[CorrectIncorrect setStringValue:#""];
[[CorrectIncorrect window] display];
Initially, each CorrectIncorrect string was being cleared to quickly, so the user never saw if their answer was 'correct' or 'incorrect'. I therefore used a time delay method to slow the process in order to allow the user to see the display before it was cleared.see below
- (void)TimeDelay
{
startInterval = [NSDate timeIntervalSinceReferenceDate];
stopInterval = [NSDate timeIntervalSinceReferenceDate];
while ((stopInterval - startInterval) <= 1)
{
stopInterval = [NSDate timeIntervalSinceReferenceDate];
}
}
This worked fine in Xcode 5. Does not work in Xcode 7.3.1. Any advice appreciated.
Few things:
1) Time interval is in seconds, so <= 1 really isn't much time, have you tried increasing that time and seeing what it looks like?
2) You should also consider adding a button like "Go to next question". That way you won't need to have a timer
3) If you want to have a timer to reset the string, you should just use Grand Central Dispatch
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[CorrectIncorrect setStringValue:#""];
});
one-ish line, and is more intuitive
I have an NSView who draw something every second. Also, i have a setting window with some controls. The problem is that: When i want change settings, my view blocked.
What can i do?
For update the view I use NSTimer :
NSTimer.scheduledTimerWithTimeInterval(refreshInterval, target: self, selector:"updateView", userInfo: nil, repeats: true)
and, in updateView function I call myView.needsDisplay = true ;
Is very annoying to try open an popUpButton and myView to freeze.
Have you any idea what can I do?
Thanks!
I've made a game in Xcode 6, using several NSTimers for different things, like a scorer timer, countdown timer, and to move my objects around. The problem is that sometimes (it seems like) the NSTimers stop for like half a second which makes it look like it lags. Example: When the character is moving, it stops for a tiny second and then continues to move. It happens so fast, but it is noticable, and it annoys me so much. I want it to be completely smooth. Any help would be appreciated!
A couple of thoughts:
If you're having a small delay in the timer processing, the most likely issue is that you have something blocking the main queue. Take a careful look at your code and see if you can find anything that could block the main queue.
You can actually use Instruments to find places in your app where the thread might be blocked. If I recall correctly, WWDC 2112 video Building Concurrent User Interfaces on iOS shows the trick with Instruments to find where your app is blocked. It's a bit dated, but the techniques for finding where the main thread blocks still apply.
It's unlikely, but you might want to consider checking the run loop modes that your timer is running on. For example, the default:
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
This can pause during certain animations. You might consider using a broader array of run loop modes, e.g.:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(tick:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
This results in a timer that is less susceptible to certain delays during particular types of animations. It just depends upon what else your app is doing when you see the delay in the user interface.
When using a timer to update animations, better than a NSTimer is a CADisplayLink. For example, define a few properties:
#property (nonatomic, strong) CADisplayLink *displayLink;
#property (nonatomic) CFTimeInterval startTime;
Then you can write code to start and stop the display link:
- (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;
// update your UI, not on the basis of "this is called x times per second",
// but rather, on the basis that `elapsed` seconds have passed
}
The key in good animation code is that you don't just assume that your routine will be called at a specified frequency, but rather that you update the UI based upon the number of elapsed seconds. This way, a slow device that drops a few frames and a fast device will yield the same animation, the latter would just be a little smoother than the former.
The merits of display links, though, are discussed briefly in WWDC 2014 video - Building Interruptible and Responsive Interactions. There are other longer discussions of the topic that are eluding me at this point, but this might be a good place to get introduced to the topic (even though the vast majority of that video is on other topics).
You may want to try a high resolution timer, like Timer dispatch sources. It looks a bit scary at first, but actually quite easy to use. Sample code (with comments)
dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue , dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
// Use dispatch_time instead of dispatch_walltime if the interval is small
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30 * NSEC_PER_SEC, 1 * NSEC_PER_SEC, dispatch_get_main_queue(), ^{
NSLog(#"Timer fired!");
});
// Keep a reference if you want to, say, stop it somewhere in the future
}
EDIT:
In XCode, if you type dispatch in the editor, it will suggest a snippet called dispatch_source timer - GCD: Dispatch Source (Timer), which will generate the template code for the timer.
I've made a RunLoop with a timer that updates a label that displays a countdown. I need the RunLoop to stop once the countdown reaches zero, for the case where the the timer finishes normally I could just use runUntilDate, with the date being the current date + the time on the countdown. The problem is when the user cancels the countdown from a button before it's finished. I don't know how to tell the RunLoop to stop from the cancel button action. Here's the code for the RunLoop:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(updateCountdownLabel:)]];
[invocation setTarget:self];
[invocation setSelector:#selector(updateCountdownLabel:)];
[[NSRunLoop mainRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 invocation:invocation repeats:YES] forMode:NSRunLoopCommonModes];
The method just tells the label to reduce by 1 in each loop.
I could tell the cancel button to change the label to zero, and have the run loop selector check if the value is zero, but could the RunLoop's own selector tell it to stop?
cancelPerformSelector:target:argument:
cancelPerformSelectorsWithTarget:
These are the closest I've found but they don't seem to work from inside the RunLoops own selector, or at least not in any way I've tried them.
Basically I need to have the button tell the RunLoop to stop, or somehow stop the RunLoop from it's own selector.
Thanks.
You haven't made a run loop, you've scheduled a timer to begin on the main run loop.
What you should do is store the NSTimer object that you create as an instance variable before scheduling the timer on the run loop.
In your updateCountdownLabel: method, once your end condition has been satisfied just call -invalidate on your timer instance. This will remove the timer from the run loop, and because you never retained it, it will be released.
I've updated the methods to use a selector-based NSTimer rather than your NSInvocation-based one. This means that the callback method signature is defined as you are expecting. It also avoids the need to store the NSTimer object in an ivar:
- (void)startCountDown
{
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateCountdownLabel:) userInfo:nil repeats:YES]
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)updateCountdownLabel:(NSTImer*)timer
{
if(thingsAreAllDone)
{
[timer invalidate];
}
}
Compiling in XCode 3.1.1 for OSX 10.5.8 target, 32-bit and i386 build.
I have a modal run loop, running in NSWindow wloop and NSView vloop. The modal loop is started first. It starts, runs and stops as expected. Here's the start:
[NSApp runModalForWindow: wloop];
Then, when the user presses the left mouse button, I do this:
if (ticking == 0) // ticking is set to zero in its definition, so starts that way
{
ticking = 1; // don't want to do this more than once per loop
tickCounter = 0;
cuckCoo = [NSTimer scheduledTimerWithTimeInterval: 1.0f / 10.0f // 10x per second
target: self // method is here in masterView
selector: #selector(onTick:) // method
userInfo: nil // not used
repeats: YES]; // should repeat
}
Checking the return of the call, I do get a timer object, and can confirm that the timer call is made when I expect it to be.
Now, according to the docs, the resulting NSTimer, stored globally as "cuckCoo", should be added to the current run loop automagically. The current run loop is definitely the modal one - at this time other windows are locked out and only the window with the intended mouse action is taking messages.
The method that this calls, "onTick", is very simple (because I can't get it to fire), located in the vloop NSView code, which is where all of this is going on:
- (void) onTick:(NSTimer*)theTimer
{
tickCounter += 1;
NSLog(#"Timer started");
}
Then when it's time to stop the modal loop (which works fine, btw), I do this:
[cuckCoo invalidate];
[NSApp stop: nil];
ticking=0;
cuckCoo = NULL;
NSLog(#"tickCounter=%ld",tickCounter);
ticking and tickCounter are both global longs.
I don't get the NSLog message from within onTick, and tickCounter remains at zero as reported by the NSLog at the close of the runloop.
All this compiles and runs fine. I just never get any ticks. I'm at a complete loss. Any ideas, anyone?
The problem is related to this statement "The current run loop is definitely the modal one". In Cocoa, each thread has at most one runloop, and each runloop can be run in a variety of "modes". Typical modes are default, event tracking, and modal. Default is the mode the loop normally runs in, while event tracking is typically used to track a drag session of the mouse, and modal is used for things like modal panels.
When you invoke -[NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] it does schedule the timer immediately, but it only schedules it for the default runloop mode, not the modal runloop mode. The idea behind this is that the app generally shouldn't continue to run behind a modal panel.
To create a timer that fires during a modal runloop, you can use -[NSTimer initWithFireDate:interval:target:selector:userInfo:repeats:] and then -[NSRunLoop addTimer:forMode:].
The answer specific to...
[NSApp runModalForWindow: wloop];
...is, after the modal run loop has been entered:
NSRunLoop *crl;
cuckCoo = [NSTimer timerWithTimeInterval: 1.0 / 10
target: self
selector: #selector(onTick:)
userInfo: nil
repeats:YES];
crl = [NSRunLoop currentRunLoop];
[crl addTimer: cuckCoo forMode: NSModalPanelRunLoopMode];
(crl obtained separately for clarity) Where the onTick method has the form:
- (void) onTick:(NSTimer*)theTimer
{
// do something tick-tocky
}