Action after progress bar loads - objective-c

my progress bar is coded this way:
.h file
#interface ViewController : UIViewController {
IBOutlet UIProgressView *wait_progress;
double wait_time;
NSTimer *wait_timer;
}
-(void) increase_waiting_time;
.m file
-(void) increase_waiting_time {
wait_time = wait_time + 0.01;
wait_progress.progress = wait_time;
}
-(IBAction)start:(id)sender {
wait_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(increase_waiting_time) userInfo:nil repeats:YES];
}
How is it possible to do some action immediately after progress bar loads (that means wait_time will equals 1)?

A simple if statement will do the job. I guess you're falling foul of float number comparisons (testing for equality) which are problematic. Have a google on the subject. There are also answers on SO (like this one).
Basically, don't check for equality - it won't work. Instead, decide how accurate the comparison needs to be and test <= and >= to verify that the value is in that range. For you it may be good enough to simply use >= 1.

Related

iOS - Execute a Piece of Code for 10 Seconds

I'd like to execute a piece of code every 500ms for a total of 10 seconds. I have the code working executing every half a second with this code
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(getLevel:) userInfo:nil repeats: YES];
but I cannot seem to figure out how to stop it executing after 10 seconds. The "repeats" argument doesnt seem to allow for a specified time.
Could someone point me in the direction of where I should go with this?
Thanks
Brian
You need to message invalidate on the NSTimer that scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: gives you. For example assign the NSTimer that method returns to you in a property or iVar of your class and after 10 seconds call invalidate on it.
Example:
.h:
#interface myClass{
NSTimer *myT;
}
[...]
.m:
[...]
{
myT = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(getLevel:) userInfo:nil repeats: YES];
[NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(stop) userInfo:nil repeats:NO];
}
-(void)stop{
[myT invalidate];
}
[...]
You could avoid another NSTimer if you want by implementing some kind of finish flag and track how many times you messaged the getLevel method, since it's every .5 seconds, that'll be 20 times.
But I would rather 2 NSTimer objects because you know it's 10 seconds regardless of the other timer, which you might decide to change, up or down it's frequency...
Another way you could go would be to define a selector, say -(void)stopTimer{}
in which you invalidate the timer. Then when you create the Timer in the first place do one of these:
[self performSelector:#selector(stopTimer) withObject:nil afterDelay:10.0];
The best thing to do is make a property on your class.
#property (nonatomic, strong) NSTimer *timer;
#property (nonatomic, strong) int ticks;
then in your getLevel selector call this:
if (ticks >= 20) [self.timer invalidate];
// 20 is 10 seconds * 500 milliseconds
ticks++;
You can set another timer to stop it using invalidate. Just keep a reference for this timer in an ivar or property.
Check this answer for more information.

Objective-C: Redrawing objects on screen

I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.

Advice on dynamically altering default core data model content

I've added the following image to help illustrate the problem better:
Hi,
I'm looking for the best starting point to alter the data stored my core data model directly - speaking as someone who's new to the area. From my reading I'm pretty confident I shouldn't touch my NSArrayController, which was my natural instinct, and that I should always tackle the model. This makes sense but because I've used bindings and core data, xcode has generated everything for me and I don't have a sense of building up a class from scratch myself.
For my initial task, I have a 'jobs' entity and NSArrayController. It has a jobTotalHours attribute that's a string in the 00:00:00 format and has a corresponding 'Hours' column for each job in an NSTableView. Separate to this, I have a stopwatch button that's linked to a text field next to it, displaying time as a 00:00:00 string. I have a class working that starts and stops a timer counting and displays it in increments of hours, minutes and seconds.
What I need to do is to make the timer add time onto the jobTotalHours attribute for the current job highlighted in the NSTableView. The separate textfield has now been bound to display the time of the current highlighted hours column so that part's taken care of. In other words, the timer was originally adding time to a test variable and displaying it in an autonomous text field for testing reasons. Now I need it to add time onto whatever job is highlighted in a table view and I need to access the model programmatically without being sure of what step to take first.
Thanks in advance for any advice. I'll include the timer class below if it's any use. I'm pretty sure it's rough and bad but it works:
timerController.h:
#import <Cocoa/Cocoa.h>
BOOL timerStarted;
int timerCount;
int timerSeconds;
int timerMinutes;
int timerHours;
NSString *timerString;
NSString *timerFieldSeconds;
NSString *timerFieldMinutes;
NSString *timerFieldHours;
#interface timerController : NSObject <NSApplicationDelegate> {
NSWindow *window;
NSTimer *timerNoOne;
IBOutlet NSCell *timerOneOutputLabel;
IBOutlet id timerClockField;
}
-(IBAction)toggleTimerClock:(id)sender;
#property (assign) IBOutlet NSWindow *window;
#end
timerController.m:
#import "timerController.h"
#implementation timerController
-(IBAction)toggleTimerClock:(id)sender
{
if (timerStarted==FALSE) {
timerStarted = TRUE;
} else {
timerStarted = FALSE;
}
}
#synthesize window;
- (void) awakeFromNib {
// clear timer
[timerClockField setStringValue:#"00:00:00"];
// initialize timer to count each second
timerNoOne = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(updateTimerNoOne:)
userInfo:nil
repeats:YES];
}
- (void) updateTimerNoOne:(NSTimer *) timer {
if (timerStarted==FALSE) {
// do nothing. Timer is switched off.
} else {
timerCount = timerCount + 1;
timerSeconds = fmod(timerCount, 60);
timerMinutes = floor(timerCount / 60);
timerHours = floor(timerCount / 3600);
if (timerSeconds < 10) { // add a leading 0 for formatting reasons.
timerFieldSeconds = [NSString stringWithFormat:#"0%d",timerSeconds];
} else {
timerFieldSeconds = [NSString stringWithFormat:#"%d",timerSeconds];
}
if (timerMinutes < 10) {
timerFieldMinutes = [NSString stringWithFormat:#"0%d",timerMinutes];
} else {
timerFieldMinutes = [NSString stringWithFormat:#"%d",timerMinutes];
}
if (timerHours < 10) {
timerFieldHours = [NSString stringWithFormat:#"0%d",timerHours];
} else {
timerFieldHours = [NSString stringWithFormat:#"%d",timerHours];
}
NSString *timerString = [NSString stringWithFormat:#"%#:%#:%#",timerFieldHours,timerFieldMinutes,timerFieldSeconds];
//[timerClockField setStringValue:timerString];
}
}
#end
Update:
From reading some more, I'm wondering if it's a better approach for me to update the string in the textcell itself on each second of timer change and then only commit changes to the model on the timer finishing (e.g. the clock was stopped). Previously I was thinking of saving the model's jobTotalHours string second by second as this was directly altering the model and avoiding controllers, which I thought was the advised route to take.
Update:
I had a subclass set up for NSTableView and NSArrayController. I was able to use them to detect selection changes to the rows in the table and print them out to the console. The subclass was called:
#interface modelUtilController : NSObject
Which performed the above tasks fine. I now wanted an outlet to the NSManagedObject so that I could directly manipulate assets in it while keeping outlets to the NSTableView to detect changed in row selection. I read that the subclass should be
#interface modelUtilController : NSManagedObject
which I changed it to and included an outlet to the data model. This crashes the original detection for changes in row selection, so I'm doing something wrong now. Perhaps I have to separate the subclass into 2?
Update : Possibly Complete
Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.
Ok I think I've solved this after 3 days at it. As far as I can see it's working but I haven't put it fully to work yet. Basically I created a separate function that I call from my timer once every second:
void amendTotalHours(id anObject)
This function uses my jobs NSArrayController and then finds the current value in the hours column using:
NSArray *selectedObjectsArray = [anObject selectedObjects];
NSManagedObjectModel *firstSelectedObject = [selectedObjectsArray objectAtIndex:0];
NSString *readCurrentTime = [firstSelectedObject valueForKey:#"jobTotalHours"];
I then convert the string of time formatted into 00:00:00 to an integer of the total seconds. I add one onto this for each call from the timer and then convert the seconds back into a string in the 00:00:00 format. Finally, I send this back to the NSArrayController using:
[firstSelectedObject setValue:[NSString stringWithFormat:#"%#", timeValue] forKey:#"jobTotalHours"];
And cry a (maybe temporary) sigh of relief.

Objective C: constantly calling a function

I'm trying to make a pretty simple game and I'm stuck. Basically I want to make a UIImageView appear every 2 seconds. My problem is I can't keep track of cumulative time. Right now I have this:
NSDate *date = [NSDate date];
NSTimeInterval secondsSinceNow = [date timeIntervalSinceNow];
NSLog(#"date = %#", date);
NSLog(#"secondsSinceNow = %f", secondsSinceNow);
It's in my button function so its called when the user taps the button. It returns a decimal number always less than 1. I've tried it in the viewDidLoad method as well as it's own method but neither work.
I think it would work if its in it's own method that is check constantly, but I don't know how to do that.
In short, I need a timer/counter that updates every second.
#interface className
{
NSTimer * timer;
}
#property (nonatomic, retain) NSTimer * timer;
#end
#implementation className
#synthesize timer;
...
/*factory method was corrected here. should work without warnings by copying and pasting */
-(void) applicationDidFinishLaunching : (UIApplication *) application {
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(targetMethod:) userInfo:nil repeats: YES];
}
//define the target method
/*method was corrected because it needed parentheses around NSTimer */
-(void) targetMethod: (NSTimer *) theTimer {
NSLog(#"Me is here at 1 minute delay");
}
..
#end
taken from here
http://www.iphonedevsdk.com/forum/iphone-sdk-development/14403-nstimer-examples.html
If those first two lines are called immediately after each other, then it (should) always be less than a second. The date is being instantiated right there, and then the timeIntervalSinceNow is called immediately on it, when little/no time has occured between them. The goal is to make the date when first called, and then call the timeIntervalSinceNow on that to get times more than 0. However, this still has no creation of a updating timer as you want.
You could simply use an NSTimer to call a selector within your class at the required two second interval.
That said, you could possibly also make use of a CABasicAnimation to fade the opacity of the UIImageView, pending on the effect you require.

Problems invalidating & re-creating NSTimer(s)

I'm having problems starting & stopping NSTimers. The docs say that a timer is stopped by [timer invalidate];
I have a timer object declared as such
.h
NSTimer *incrementTimer;
#property (nonatomic, retain) NSTimer *incrementTimer;
.m
#synthesize incrementTimer;
-(void)dealloc {
[incrementTimer release];
[super dealloc];
}
-The usual.
When it's needed, my method does the following:
-(void)setGenCount {
if(!condition1 && condition2) {
incrementTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector:#selector(incrementBatteryVoltage:)
userInfo: nil
repeats: YES];
}
}
Everything above works fine. However, once that timer does it's job, I want it to invalidate itself. I invalidate the timer because there is an equal decrement method that could be called and would fight against the incrementTimer if it was still active. (Previously, I noticed that my two timers, if active, were acting on the same ivar by increasing & decreasing the value (a sort of fight)... without crashing) The selector called works as follows:
-(void)incrementBatteryVoltage:(NSTimer *)timer {
if(battVoltage < 24.0) {
generatorDisplay.battVoltage += 0.1;
}
if(battery1Voltage == 24.0) {
[timer invalidate];
}
}
I have an equal method that Decrements the battery count. (previously mentioned)
Due to my program design: the interface simulates a voltage display. When the "machine" is turned off, I want all the timers invalidated, regardless of what any voltage value is. I'm doing this by checking to see if the timer is valid.
-(void)deEnergizeDisplays {
if([decrementTimer isValid]) {
[decrementTimer invalidate];
decrementTimer = nil;
}
if([incrementTimer isValid]) {
[incrementTimer invalidate];
incrementTimer = nil;
}
I'm getting numerous "BAD_ACCESS" crashes. The erroneous line call is always pointing toward my [timer isValid] call. It seems that if the timer is invalidated... the pointer
doesn't exist either. I know that the [timer invalidate] message disables the timer and then it is removed from the run loop and then it is released. And my understanding is: it is an autoreleased object per it's naming covention.
My thought are: If I'm sending a retain message, shouldn't the reference still exist? I've tried several combinations, taking away:
timer = nil;
or even instead of:
if([timer isValid])
I tried :
if([timer != nil])
and:
if(timer)
I always get the same crash. Thanks for any help on starting & stopping NSTimers.
UPDATE: See Darren's answer. The problem is that you are not using your property accessor when setting the timers. Instead of:
incrementTimer = [NSTimer ...
You should have:
self.incrementTimer = [NSTimer ...
The self.propertyName = ... syntax will call your accessor method, and thereby automatically retain the object that you send to it (since your property is set up as retain). Simply calling propertyName = ... does not use the property accessor. You are simply changing the value of your ivar directly.
UPDATE #2: After an enlightening conversation with Peter Hosey (see comments), I have removed my earlier suggestion to "never retain or release" your timer object. I have also completely re-written my earlier code because I think the following is a better approach:
Controller.h:
NSTimer *voltageTimer;
float targetBatteryVoltage;
...
#property (nonatomic, retain) NSTimer *voltageTimer;
Controller.m:
#implementation Controller
#synthesize voltageTimer;
- (void)stopVoltageTimer {
[voltageTimer invalidate];
self.voltageTimer = nil;
}
- (void)setTargetBatteryVoltage:(float)target {
[voltageTimer invalidate];
targetBatteryVoltage = target;
self.voltageTimer = [NSTimer scheduledTimerWithTimeInterval: 2.0
target: self
selector: #selector(updateBatteryVoltage:)
userInfo: nil
repeats: YES];
}
- (void)updateBatteryVoltage:(NSTimer *)timer {
const float increment = 0.1;
if (abs(battVoltage - targetBatteryVoltage) < increment) {
[timer invalidate];
}
else if (battVoltage < targetBatteryVoltage) {
generatorDisplay.battVoltage += increment;
}
else if (battVoltage > targetBatteryVoltage) {
generatorDisplay.battVoltage -= increment;
}
}
Now, you can simply set a target battery voltage, and the timer magic will happen behind the scenes:
[self setTargetBatteryVoltage:24.0];
Your power-off method would look as follows:
- (void)deEnergizeDisplays {
[self stopVoltageTimer];
}
You need to retain the value assigned to incrementTimer in setGenCount. You can do this automatically by using your synthesized property, which is accessed via self.:
self.incrementTimer = [NSTimer scheduledTimerWithTimeInterval: ...