OSX - cannot set an NSTextField with a string from an NSTimer - macos

I cannot seem to set a timer output to an NSTextField. I've looked at this Q/A, among many, but must be doing something different but wrong.
I initialize the timer and the textfield in WindowDidLoad.
My timer method is as follows:
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%d:0%d",(time/60),(time%60)];
[timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%d:%d",(time/60),(time%60)];
timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", timerTextField.stringValue);
As you can see, I log the textfield output.
I have an IBOutlet connection from the the File's owner to the timerTextField.
I can see the output correctly in the log, but the textfield string is not populated.
Can anyone see anything I'm doing wrong? Thanks

The code you posted looks OK to me. (You could replace it by binding your timerTextField to the time value via Cocoa Bindings though).
How did you create your timer and to which runloop(mode) did you add it?
Another problem could be that the outlet to your timerTextField is not connected. Can you set a breakpoint in your timer selector and check if timerTextField is not nil.
Update:
I just tried your code and for me it works.
Given that you have an IBOutlet named timerTextField connected to a NSTextField instance in Interface Builder, this code updates the textfield each second:
#interface SSWAppDelegate ()
{
NSUInteger time;
}
#property (weak) IBOutlet NSTextField *timerTextField;
#end
#implementation SSWAppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateTime:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void)updateTime:(id)sender
{
time += 1;
if((time % 60) <= 9) {
NSString *tempString = [NSString stringWithFormat:#"%lu:0%lu",(time/60),(time%60)];
[self.timerTextField setStringValue:tempString];
} else {
NSString *tempString = [NSString stringWithFormat:#"%lu:%lu",(time/60),(time%60)];
self.timerTextField.stringValue = tempString;
}
NSLog(#"timerTextField: %#", self.timerTextField.stringValue);
}
#end

Related

Using A NSTimer for MM:SS:HS?

I have created a NSTimer In Xcode 4.2 and it works but i get this one problem.
here is my project in the simulator
when i press start it starts and when i press stop it stops and when its stopped it will reset
but when it starts and i press reset when it is going nothing happens it don't reset when started basically you have to stop then reset is the ways and this or do i need to add code any where heres a copy of my code.
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController {
IBOutlet UILabel *time;
NSTimer *myticker;
//declare baseDate
NSDate* baseDate;
}
-(IBAction)stop;
-(IBAction)reset;
#end
heres my implementation
#import "FirstViewController.h"
#implementation FirstViewController
#synthesize baseDate;
-(IBAction)start {
[myticker invalidate];
self.baseDate = [NSDate date];
myticker = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
}
-(IBAction)stop;{
[myticker invalidate];
myticker = nil;
}
-(IBAction)reset {
self.baseDate = [NSDate date];
time.text = #"00:00:0";
}
-(void)showActivity {
NSTimeInterval interval = [baseDate timeIntervalSinceNow];
double intpart;
double fractional = modf(interval, &intpart);
NSUInteger hundredth = ABS((int)(fractional*10));
NSUInteger seconds = ABS((int)interval);
NSUInteger minutes = seconds/60;
time.text = [NSString stringWithFormat:#"%02d:%02d:%01d", minutes%60, seconds%60, hundredth];
}
I Really Appreciate It. Thanks.
First, the above should crash with EXC_BAD_ACCESS when it reaches showActivity since baseDate is not being retained in the start method. [NSDate date] returns an autoreleased object so baseDate will have an invalid reference after the start method.
I suggest changing baseDate to a retain property and then setting it in start using self.:
//.h
#property (nonatomic, retain) NSDate *baseDate;
//.m
#synthesize baseDate;
-(IBAction)start {
[myticker invalidate];
self.baseDate = [NSDate date];
myticker = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(showActivity) userInfo:nil repeats:YES];
}
To fix the reset problem, note that the showActivity method takes the current value of baseDate to calculate the elapsed time and then sets the time label to display it formatted.
In the start method, you set the baseDate to the current time (you don't set time.text) and then start the timer. The showActivity method will then keep firing and set time.text.
In the reset method, you want the timer to start showing the elapsed time since the moment reset is pressed. The timer is already running so you don't need to re-start it. Setting the time label text doesn't work because when the already-running timer fires again, it will calculate the elapsed time from baseDate which is still the original start time and then set time.text based on that. So instead of setting time.text, set the baseDate:
-(IBAction)reset {
self.baseDate = [NSDate date];
}

EXC_BAD_ACCESS when I close my window, which is also my application's delegate

I wrote a Cocoa Application and I got EXC_BAD_ACCESS error when I'm closing an application window. I read that this error usually means problems with memory, but I have ARC mode on and I don't need care about releasing e.t.c. (xCode forbids me to call this functions and manage memory automatically).
Error is pointing at line return NSApplicationMain(argc, (const char **)argv); in main function.
Here's my application's code:
.h file:
#interface MainDreamer : NSWindow <NSWindowDelegate>
{
NSTextField *dreamField;
NSTableView *dreamTable;
NSImageView *dreamview;
NSMutableArray *dreamlist;
NSMutableArray *dataset;
}
#property (nonatomic, retain) IBOutlet NSTextField *dreamField;
#property (nonatomic, retain) IBOutlet NSTableView *dreamTable;
#property (nonatomic, retain) IBOutlet NSImageView *dreamview;
#property (nonatomic, retain) IBOutlet NSMutableArray *dreamlist;
#property (nonatomic, retain) IBOutlet NSMutableArray *dataset;
#property (assign) IBOutlet NSWindow *window;
#end
.m file:
#implementation MainDreamer
#synthesize window;
#synthesize dataset;
#synthesize dreamField;
#synthesize dreamlist;
#synthesize dreamview;
#synthesize dreamTable;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
NSString *applicationPath = [[NSBundle mainBundle] bundlePath];
NSString *filename = [applicationPath stringByAppendingPathComponent:#"dreams"];
NSLog(self.description);
dreamlist = [[NSMutableArray alloc] init];
dataset = [[NSMutableArray alloc] init];
dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
if([dataset count] != 0) {
int i = 0;
while (i < [dataset count]) {
Dream *dr = [[Dream alloc] init];
dr = [dataset objectAtIndex:i];
[dreamlist addObject: dr.dreamname];
i++;
}
}
[dreamTable reloadData];
}
-(void)applicationWillTerminate:(NSNotification *)notification{
NSString *applicationPath = [[NSBundle mainBundle] bundlePath];
NSString *filename = [applicationPath stringByAppendingPathComponent:#"dreams"];
[NSKeyedArchiver archiveRootObject:dataset toFile:filename];
NSLog(#"finish");
}
- (void) mouseUp:(NSEvent *)theEvent{
long index = [dreamTable selectedRow];
Dream *dr = [[Dream alloc] init];
dr = [dataset objectAtIndex:index];
dr.dreampicture = dreamview.image;
[dataset replaceObjectAtIndex:index withObject:dr];
NSLog(self.description);
}
- (void) tableViewSelectionDidChange: (NSNotification *) notification{
long row = [dreamTable selectedRow];
Dream *dr = [[Dream alloc] init];
dr = [dataset objectAtIndex: row];
if(dr.dreampicture != NULL)
dreamview.image = dr.dreampicture;
NSLog(#"selected row changed");
}
Class "Dream":
#interface Dream : NSObject <NSCoding>
{
NSString *dreamname;
NSImage *dreampicture;
}
#property (retain) NSString* dreamname;
#property (retain) NSImage* dreampicture;
-(id)initWithCoder:(NSCoder *)aDecoder;
-(void)encodeWithCoder:(NSCoder *)aCoder;
#end
What is wrong, why EXC_BAD_ACCESS occurs?I remind that I have xCode 4 with Automatic Reference Counting (ARC)
Thanks
UPDATE
I used Profile to find zombie event. So I found out this: An Objective-C message was sent to a deallocated object(zombie( at adress 0x108d85230)
Responsible Caller - [NSApplication(NSWindowCache) _checkForTerminateAfterLastWindowClosed: saveWindows:]
I had this function in code:
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender{
return TRUE;
}
However after I putted it in comments, this zombie event continue to occur.
The crash is caused by the fact that you made the window your application's delegate. When you close the window, that is the last release that kills it off, and if it's the last window you had up, it causes the application to ask its delegate whether it should quit. Since the window you just killed off is the application's delegate, you get that crash.
Longer explanation and suggestion of solution in my answer on your subsequent question.
This is wrong:
dataset = [[NSMutableArray alloc] init]; // WRONG
dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
Why? You first allocate an empty array, and store that in the instance variable dataset. But in the next line, you replace the empty array with whatever +unarchiveObjectWithFile: returns. Why is this a problem? Well, if you read the docs, you'll see that it returns nil if the file is not found. This means that you now replace the empty array with nil, and all messages you send to dataset will be ignored (messages to nil are silently ignored in Objective-C)
I assume you actually want load the dataset from file, and only if that failed, start with an empty dataset:
dataset = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
if (dataset==nil) dataset = [[NSMutableArray alloc] init];
You have a similar error later on:
Dream *dr = [[Dream alloc] init]; // WRONG
dr = [dataset objectAtIndex:index];
You create a Dream object, and then replace it immediately with something from the dataset. What you actually want to do is:
Dream *dr;
dr = [dataset objectAtIndex:index];
or shorter:
Dream *dr = [dataset objectAtIndex:index];
Then again, you could replace the while loop with a fast-enumeration-style for loop:
for (Dream *dr in dataset) {
[dreamlist addObject: dr.dreamname];
}
Finally, to get to a point, I don't think the EXC_BAD_ACCESS actually occurs in main.h. I assume you use Xcode 4. Please use the thread/stack navigator in the right sidebar when debugging to find the actual position where the error occurs.
It could be that the error actually occurs in applicationWillTerminate:, because you try to archive dataset, which is probably nil, and it's probably not allowed to archive nil.
With ARC you should use strong and weak instead of retain and assign.

NSOpenGLView, NSWindow & NSResponder - makeFirstResponder not working

In the code below I am Initialising a NSViewController [a NSResponder], with a NSWindow, a NSOpenGLView, presenting the view and attempting to set the NSViewController as the windows first responder.
It is not working. I was expecting to be able to hit a breakpoint in the keyUp: and keyDown: methods also below, but nothing is happening.
Am I missing something?
-(void)initwithFrame:(CGRect)frame
{
window = [[MyNSWindow alloc] initWithContentRect:frame styleMask:NSClosableWindowMask | NSTitledWindowMask backing:NSBackingStoreBuffered defer: YES ];
OpenGLView* glView = [[[OpenGLView alloc] initWithFrame:window.frame] autorelease];
window.contentView = glView;
[window makeFirstResponder:self];
[window makeKeyWindow];
[window display];
}
-(void)keyDown:(NSEvent*)theEvent
{
unichar unicodeKey = [ [ theEvent characters ] characterAtIndex:0 ];
unicodeKey = 0;
}
-(void)keyUp:(NSEvent *)theEvent
{
unichar unicodeKey = [ [ theEvent characters ] characterAtIndex:0 ];
unicodeKey = 0;
}
Returning to this issue, actually the problem is elsewhere.
I had been using this sleep function to control the apps frame rate:
void System::Sleep(double seconds)
{
NSDate* limit = [NSDate dateWithTimeIntervalSinceNow:seconds];
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
[runLoop runUntilDate:limit];
}
Doing this seems to freeze the system completely and block the key events.
Instead use this to schedule the update function:
[NSTimer scheduledTimerWithTimeInterval:interval target:self selector:#selector(updateApp:) userInfo:nil repeats:YES];
For instances to participate in key-view loops, a custom view must return YES from acceptsFirstResponder.
I had this problem also. This thread might help
keyDown not being called
I found out what was preventing the keyDown event from being called.
It was the NSBorderlessWindowMask mask, it prevents the window from
become the key and main window. So I have created a subclass of
NSWindow called BorderlessWindow:
#interface BorderlessWindow : NSWindow { }
#end
#implementation BorderlessWindow
- (BOOL)canBecomeKeyWindow {
return YES; }
- (BOOL)canBecomeMainWindow {
return YES; }
#end

Xcode4 How do I play one timer and jump to another then back as a loop?

It's a bit more complex however...
I need two NSTimers:
myTimer1 will have a speed of 1 sec. intervals and play for 6
intervals stop and jump to myTimer2...
myTimer2 will have a speed of 2 sec. intervals and play for 6
intervals and then jump back to myTimer1
and continue this until a stop button is touched.
I have managed to only get the one timer to play and can figure out how to do the rest.
Here is the code synopsis I used:
.h
#interface sosTest8ViewController : UIViewController {
IBOutlet UILabel *myCounterLabel;
IBOutlet UIView *greenView;
IBOutlet UIView *blueView;
NSTimer *myTimer1;
NSTimer *myTimer2;
}
#property (nonatomic, retain)NSTimer *myTimer1;
#property (nonatomic, retain)NSTimer *myTimer2;
#property (nonatomic, retain) UILabel *myCounterLabel;
#property (nonatomic, retain) UIView *greenView;
#property (nonatomic, retain) UIView *blueView;
#end
.m
#synthesize myCounterLabel;
#synthesize greenView;
#synthesize blueView;
#synthesize myTimer1;
#synthesize myTimer2;
- (void)viewDidLoad
{
[super viewDidLoad];
self.myCounterLabel.text = #"0";
myTimer1 = [NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
-(void)updateCounter:(NSTimer*)theTimer{
static int count = 1;
count++;
NSString *s = [[NSString alloc]initWithFormat:#"%d", count];
self.myCounterLabel.text = s;
[s release];
self.blueView.hidden = YES;
self.greenView.hidden = YES;
switch (count & 0x01) {//was & 0x03
case 0: self.blueView.hidden = NO; break;
case 1: self.greenView.hidden = NO; break;
}
//I added this if bit as my own way, but doesn't jump to the net timer properly
if (count >= 3) {
[myTimer1 invalidate];
self.myCounterLabel.text = #"0";
myTimer2 = [NSTimer scheduledTimerWithTimeInterval:2.0f
target:self
selector:#selector(updateCounter2:)
userInfo:nil repeats:YES];}
So this is the formula I've been playing with and before I added the "if" statement it worked and the two colored UIViews alternated fine. When I change the speed to two in myTimer1 to 2 that works in changing the speed interval. I just need help using a working syntax to add the second timer jump and then looping it.
It may not even be possible to do this, but I hope it is and someone can help me.
Many thanks.
--Rob
I would create Two NSStrings:
NSString *countone
NSString *countwo
Then in your selector for your first timer:
if ([countone isEqualToString:#""]) {
//do your stuff
countone = #"1";
}
else if ([countone isEqualToString:#"1"]) {
//do stuff
countone = #"2";
}
else if ([countone isEqualToString:#"2"]) {
//do stuff
countone = #"3";
}
and so on until you get to:
else if ([countone isEqualToString:#"6"]) {
//stop your first timer
//start your second timer
countone = #"";
}
And you can do exactly the same for your second timer, just the orher way round so that at the end you start your first timer, and like that it loops over and over!
And to stop the timer do:
[Timer invalidate];
Timer=nil;

NSTimer Troubles

I am trying to run the code below but it keeps locking up my simulator after the "Tick" is written to the console. It never outputs "Tock" so my guess is that it has to do with the line "NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];" The IBactions are activated by buttons. timer and startTime are defined in the .h as NSTimer and NSDate respectively.
Can anyone tell me what I am doing wrong?
code:
- (IBAction)startStopwatch:(id)sender
{
startTime = [NSDate date];
NSLog(#"%#", startTime);
timer = [NSTimer scheduledTimerWithTimeInterval:1 //0.02
target:self
selector:#selector(tick:)
userInfo:nil
repeats:YES];
}
- (IBAction)stopStopwatch:(id)sender
{
[timer invalidate];
timer = nil;
}
- (void)tick:(NSTimer *)theTimer
{
NSLog(#"Tick!");
NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];
NSLog(#"Tock!");
NSLog(#"Delta: %d", elapsedTime);
}
I have the following in the .h:
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate> {
NSTimer *timer;
NSDate *startTime;
}
- (IBAction)startStopwatch:(id)sender;
- (IBAction)stopStopwatch:(id)sender;
- (void)tick:(NSTimer *)theTimer;
#property(nonatomic, retain) NSTimer *timer;
#property(nonatomic, retain) NSDate *startTime;
#end
Where you have:
startTime = [NSDate date];
You need:
startTime = [[NSDate date] retain];
Anything that is created with out an alloc, new, init will be auto-released (rule of thumb). So what is happening is you are creating the NSDate, assigning it to startTime, it's getting auto-released (destroyed), then you are trying to call timeIntervalSinceNow on an object that was fully released so it blows up.
Adding the retain increased the retain count so it still sticks around after the auto-release. Don't forget to manually release it when you're done with it though!
To take advantage of the #property you need to do:
self.startTime = [NSDate date]

Resources