Xcode cocoa osx NSTextfield query - xcode

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

Related

NSTimer stops from time to time

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.

What issues could arise when using GCD dispatchAfter() in this use case

I'm going through a book on OS X programing as a refresher and have a document app set up with an array controller, tableView etc. The chapter calls for implementing undo support by hand using NSInvocation. In the chapter, they call for adding a create employee method and manually, adding outlets to the NSArrayController, and connecting my add button to the new method instead of the array controller.
Instead I did this with my method for inserting new objects:
-(void)insertObject:(Person *)object inEmployeesAtIndex:(NSUInteger)index {
NSUndoManager* undoManager = [self undoManager];
[[undoManager prepareWithInvocationTarget:self]removeObjectFromEmployeesAtIndex:index];
if (![undoManager isUndoing]) {
[undoManager setActionName:#"Add Person"];
}
[self startObservingPerson:object];
[[self employees]insertObject:object atIndex:index];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Wait then start editing
[[self tableView]editColumn:0 row:index withEvent:nil select:YES];
});
}
This works ok (looks a bit silly), but I was wondering the what issues could arise from this. I've done this elsewhere in order to execute code after an animation finished (couldn't figure out a better way).
Thanks in advance.
Why are you delaying the invocation of -editColumn:row:withEvent:select:?
Anyway, the risks are that something else will be done between the end of this -insertObject:... method and when the dispatched task executes. Perhaps something that will change the contents of the table view such that index no longer refers to the just-added employee.

NSDatePicker misbehaving with arrow keys

I have used NSDatePickers (without steppers) in the cells of a column in an NSTableView. The date pickers are used to set a duration of time in hours, minutes and seconds. If I highlight one of the controls and use the arrow keys to set the values, the date picker displays some weird behaviour:
Seconds increment by two for every time I press up arrow (whereas it should increment by one for each key press);
Seconds do not decrement when I press down arrow;
Every time I increment and decrement the minute and hour values, the seconds value also increases by one (each key press should only increment the selected value).
You can see this behaviour in the below example.
I have configured the NSDatePicker (result) as follows:
result = [[NSDatePicker alloc] initWithFrame:NSZeroRect];
result.identifier = #"timeCell";
result.tag = row;
[result setBordered:NO];
result.backgroundColor = [NSColor clearColor];
result.datePickerElements = NSHourMinuteSecondDatePickerElementFlag;
result.datePickerStyle = NSTextFieldDatePickerStyle;
result.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
[result setTarget:self];
[result setAction:#selector(timePickerDidChange:)];
Question: Does anyone know why this is happening and how I can fix it? Many thanks for your help.
PS: This question also touches on some weirdness surrounding arrow keys and NSDatePicker. It doesn't offer a solution though.

XCODE - How to update array?

I'm very new to Xcode/programming and trying to modify existing code
I'm having a small problem where I have an amount of objects (enemies) on the screen at one particular time and cannot redefine their value. I set my enemies to begin with 3 on the screen.
My objective is to change the amount of enemies based on the current score.
I've attached snippets of the code below.
int numberOfEnemies;
if (self.score>=0) {
numberOfEnemies = 3
}
else if (self.score>=100) {
numberOfEnemies = 4
}
// Setup array
enemyArray = [[NSMutableArray alloc] init];
for(int i = 0; i < numberOfEnemies; i++) {
[enemyArray addObject:[SpriteHelpers setupAnimatedSprite:self.view numFrames:3
withFilePrefix:#"enemyicon" withDuration:((CGFloat)(arc4random()%2)/3 + 0.5)
ofType:#"png" withValue:0]];
}
enemyView = [enemyArray objectAtIndex:0];
What do I need to do to parse the new value of numberOfEnemies into the array when my score updates?
I'm going to move our conversation into an answer since I don't want it to get too long winded, and I can easily edit and expand on this.
So far, we've established that the reason that you're having issues is that you execute the above code in the viewDidLoad function, which will run at least once when the application is first started. The problem with this is as you've found out, that you arent getting a chance to see a new score, and then update the number of enemies.
I know that game update loops for iOS are usually done in the following structure, but I would recommend finding a tutorial online to get what may be a more efficient/correct way to do it.
From your current structure, I would take the code you have above and create a new function out of it:
-(void) updateDifficulty:(NSTimer *)gameTimer
{
//This can be the code you have above for now
}
Afterwards, inside of your viewDidLoad, I would put the following code:
-(void) viewDidLoad:
{
gameTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateDifficulty:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
}
What that does is it declares a timer that will keep track of the game time, and with how it was declared, every 1 second it will call the updateDifficulty method. This is the general structure that you want, but again I would highly suggest you check out a game tutorial from Ray Wenderlich for example.
Hope that helps!

NSTimer - set up plain vanilla -- doesn't fire

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
}

Resources