0

I'm looking for a way to display strings at intervals in an NSTextField while my application is idle. I know of one way to do this using sleep although I don't think it's the best way to handle this. I'm thinking NSTimer scheduledTimerWithTimeInterval might be a good way to do it, but I'm not sure how to do it.

Here's my code:

- (void)showTextAtInterval {

NSTextField *textLabel;

[textLabel setStringValue:[NSString stringWithFormat:@"06/01/14"]];
sleep(5);
[textLabel setStringValue:[NSString stringWithFormat:@"Headlines"]];
....

}

EDIT: tried this, but only showed "Headlines" statically.

[self startTimer];

- (void)startTimer {

    [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(showTextAtInterval) userInfo:nil repeats:YES];
    return;
}

- (void)showTextAtInterval {

    NSTextField *textLabel;

    [textLabel setStringValue:[NSString stringWithFormat:@"06/01/14"]];
    [textLabel setStringValue:[NSString stringWithFormat:@"Headlines"]];

}
Tulon
  • 4,011
  • 6
  • 36
  • 56
ctfd
  • 338
  • 3
  • 14
  • The code shown in the question will not display anything, yet you say it shows "Headlines". What is your actual code? (Edit your question, don't post it as a comment.) – CRD May 26 '14 at 18:24
  • @CRD, That is the actual code, [here's the XCode project](https://www.dropbox.com/s/io7ppr7jiyjifg8/NSTextTimer.zip). It shows 'Headlines' when you run the project. – ctfd May 26 '14 at 18:42
  • In the above code `textLabel` is an uninitialised local variable of `showTextAtInterval` - that code will not work. In your actual code `textLabel` is an `IBOutlet` instance variable of the class. Details like this matter. I'll add an answer below. – CRD May 26 '14 at 20:37
  • Not playing devils advocate, but even if you change the instance variable to be `NSTextView *textLabel` without the `IBOutlet` it still does the same thing. – ctfd May 26 '14 at 20:50
  • The point is your code is calling a method on an uninitialised variable, anybody trying to help you will see that and point out that as the problem. Only because you wrote it produced output did I know that that isn't really your code. When you abbreviate code to fit a question you have to be careful to not introduce errors or miss out key stuff. HTH – CRD May 26 '14 at 21:24

3 Answers3

1

Initialize your timer

-(void)awakeFromNib{
    [super awakeFromNib];
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(showTextAtInterval) userInfo:nil repeats:YES];

}

And use your selector:

- (void)showTextAtInterval {

NSTextField *textLabel;

[textLabel setStringValue:[NSString stringWithFormat:@"06/01/14"]];

[textLabel setStringValue:[NSString stringWithFormat:@"Headlines"]];
....

}
nicael
  • 18,550
  • 13
  • 57
  • 90
  • Would this work not being in `awakeFromNib`? I did something like `[self startTimer];` and then put the `NSTimer` into a `-(void)startTimer`, but the `textLabel` only shows one label (the very last one, in this case "headlines") and doesn't loop or anything. thx – ctfd May 26 '14 at 14:56
  • @ctfd Then you should place `[self startTimer]` in `awakeFromNib` – nicael May 26 '14 at 15:21
  • Note the selector is incorrect, from the Apple docs: "The selector should have the following **signature: `timerFireMethod:`** (including a colon to indicate that the method takes an argument). The timer passes itself as the argument". – zaph May 26 '14 at 15:21
  • @nicael, the problem with putting the function in `awakeFromNib` is that I didn't want to display the text right away, but as needed. – ctfd May 26 '14 at 15:27
1

Your code has the right idea, just needs some changes.

First as you found out don't use sleep - this stops the current thread completely and if you call it from your main thread, as you did, essentially your application.

There are a number of ways to do this. NSTimer which you have picked is a good choice as you say you say only want to display the message(s) when your program is idle, NSTimer has an invalidate message you can use to stop the timer which you can call when your code is no longer idle.

UI updates must occur on the main thread and are not necessarily synchronous - on return from a method that updates the UI the UI may not yet reflect the change. This is why you only see "Headlines", you have two UI update calls on after the other and the second is done before you get to see the effect of the first.

What you need to do is: pause, show first message, pause, show second message, repeat as long as needed. So change your code so that on the first timer trigger you show the first message, the second time it triggers you show the second, etc. You can use an instance variable to keep track of which message should be displayed first. Better create an "idle message" class with methods to start and stop the messages and keep all the message selection and display logic within that class, your timer and other state should be instance variable of this class. Make sure all UI updates occur on the main thread, you can do that by ensuring the NSTimer is scheduled on that thread (and put the logic to ensure that in the start method).

HTH

CRD
  • 52,522
  • 5
  • 70
  • 86
  • Can you tell me why it must remain on the main thread? I'm not disputing the validity of it, but I ran into a similar issue when using a subview that used animation. It seems a bit strange that those type of things can't run in the background somehow. – ctfd May 26 '14 at 20:56
  • What is the standard way to pause the timer as you've mentioned? thx – ctfd May 26 '14 at 20:58
  • All UI updates themselves must be on the main thread, that is Apple's rule. You can run code on other threads to determine how the UI should change, draw into offscreen buffers, etc., it is just the updates themselves that need to be on the main thread. – CRD May 26 '14 at 21:17
  • You already have the timer correct, it is a repeating timer. You don't pause the timer, but you do invalidate it when you no longer need to do the idle message display (and create a new timer if you need to restart the display). – CRD May 26 '14 at 21:19
0

Just to correct, I think you should add a private var and do a switch in showTextAtInterval.

- (void)showTextAtInterval {

NSTextField *textLabel;
switch(iTimer)

{

case 0:

[textLabel setStringValue:[NSString stringWithFormat:@"06/01/14"]];

break;

case 1:

[textLabel setStringValue:[NSString stringWithFormat:@"Headlines"]];

break;

....

}

iTimer ++;

}
Tulon
  • 4,011
  • 6
  • 36
  • 56
Plokstorm
  • 27
  • 1
  • by private var, do you mean like this > http://stackoverflow.com/a/844670/3257552? and how would I use it exactly. thx – ctfd May 26 '14 at 15:17