I want to make a timer in my app, but i find a problem like this:
My code run very well at viewController which contain the code below, but after i dismiss this viewController a few seconds, Xcode will report an erro: exc_bad_instruction (code=exc_i386_invop subcode=0x0
If i didn't use dispatch_suspend(self.timer) and dispatch_resume(self.timer), everything will be ok
otherwise, I can see the error will happen when Xcode deal with [viewController .cxx_destruct]
so, anyone can tell me how to fix it?
Thank you
Here is my code,
- (void)timeButtonClick:(UIButton *)button{
if (self.isTiming == YES){
self.isTiming = NO;
self.executeTime = [self.timeButton currentTitle];
dispatch_suspend(self.timer);
}else if (self.isTiming == NO){
self.isTiming = YES;
self.createDate = [NSDate date];
dispatch_resume(self.timer);
}
}
- (dispatch_object_t)timer{
if (_timer == nil) {
dispatch_queue_t timerQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, timerQueue);
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
__weak typeof (self)weakSelf = self;
dispatch_source_set_event_handler(timer, ^{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"HH:mm:ss";
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
NSCalendarUnit unit = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;
NSDate *currentDate = [NSDate date];
NSDateComponents *currentCmps = [calendar components:unit fromDate:weakSelf.createDate toDate:currentDate options:NSCalendarWrapComponents];
NSDate *executeTime = [dateFormatter dateFromString:weakSelf.executeTime];
NSDateComponents *executeCmps = [calendar components:unit fromDate:executeTime];
NSString *newExecuteTime = [NSString stringWithFormat:#"%02ld:%02ld:%02ld", executeCmps.hour + currentCmps.hour, executeCmps.minute + currentCmps.minute, executeCmps.second + currentCmps.second];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.timeButton setTitle:newExecuteTime forState:UIControlStateNormal];
});
});
_timer = timer;
}
return _timer;
}
Move your timer to another place like AppDelegate, you are getting this error because you are dispatching the timer to another thread, then when the VC is dismissed the timer wants to set the title to timeButton which wa salready destroyed when the VC was dismissed.
You could also try to make a strong weakself, but I really suggest you to move the timer code to another class.
Related
I'm using ActionSheetDatePicker for time picker i change mode in picker as UIDatePickerModeCountDownTimer to take Hours and Minutes. It works well displayed Hours and min but when i getting Hrs and Min it return me NSDate init.
Ex. I selected 5 min to return me 300, so i get.
Here is code :
ActionSheetDatePicker *datePicker = [[ActionSheetDatePicker alloc] initWithTitle:#"Select a time" datePickerMode:UIDatePickerModeCountDownTimer selectedDate:self.selectedTime target:self action:#selector(ExtrTravelTimeWasSelected:element:) origin:sender];
datePicker.minuteInterval = minuteInterval;
[datePicker showActionSheetPicker];
and delegate
- (void)ExtrTravelTimeWasSelected:(NSDate *)selectedTime element:(id)element {
self.selectedTime = selectedTime;
}
So Is there any way to convert 300(NSDate) to Hr and Min ?
If I'm understanding correctly you're getting the value in seconds. try the following:
-(NSString *)getTimeStringFromSeconds:(double)seconds
{
NSDateComponentsFormatter *dcFormatter = [[NSDateComponentsFormatter alloc] init];
dcFormatter.zeroFormattingBehavior = NSDateComponentsFormatterZeroFormattingBehaviorPad;
dcFormatter.allowedUnits = NSCalendarUnitHour | NSCalendarUnitMinute;
dcFormatter.unitsStyle =
NSDateComponentsFormatterUnitsStylePositional;
return [dcFormatter stringFromTimeInterval:seconds];
}
Try this, I have updated your code,
- (void)ExtrTravelTimeWasSelected:(NSDate *)selectedTime element:(id)element {
self.selectedTime = selectedTime;
// Split up the date components
NSDateComponents *time = [[NSCalendar currentCalendar]
components:NSCalendarUnitHour | NSCalendarUnitMinute
fromDate:selectedTime];
NSLog(#"h:mm::%zd:%2zd",[time hour],[time minute]);
}
I have an online radio. The radio has programs at different times, programs are displayed in a list.
My need is that when one tap and hold on the list of program he schedule a notification to the User.
examplo program in list:
Tap and hold is already running (code below):
-(void)registerHour:(UILongPressGestureRecognizer *)gestureRecognizer
{
CGPoint ponto = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:ponto];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.imageAgendamento.hidden = false;
NSString *test = [ NSString stringWithFormat:#"%#",[[results objectAtIndex:indexPath.row] objectForKey:#"hour" ]];
NSLog(#"hour -> %#", test);
}
}
The test is where has the time of notification "09:00" (in direct
format JSON)
appDelegate.m i add:
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}
Looked at several tutorials and researched in various places, I could not solve my problem any way. What I need to do to get this notification schedule?
You can send a 5 min delayed local notification using the code below:
NSDateComponents *dateComponents = [[NSDateComponents alloc] init];
NSDate *currentDate = [NSDate date];
[dateComponents setMinute:5];
NSDate *fireDate = [gregorian dateByAddingComponents:dateComponents toDate:currentDate options:0];
UILocalNotification *localNotification = [[UILocalNotification alloc] init];
// Configure the notification
// ....
//
localNotification.fireDate = fireDate;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotification];
But I think what you need to do is using an NSTimer or simply the performSelector:withObject:afterDelay: method of NSObject.
I have this code:
NSDate *now = [NSDate date];
static NSDateFormatter *dateFormatter;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"h:mm:ss";
}
recordStatusLabel.text = [dateFormatter stringFromDate:now];
NSLog(#"Time now: %#", [dateFormatter stringFromDate:now]);
that counts the current time. How can i change it to start in this format?
00:00:00 (hours:minutes:seconds)
from a NSString variable:
Example: i got this value for my variable
NSString * time = #"02:16:23";
then the counter will continue the count to:
02:16:24
.
.
.
02:20:13
Create an instance of NSTimer class and set time for event fire 1 second with repeat option YES. In the event handling update your label with current time. When your functionality is complete, invalidate the timer to stop firing events.
Here is the code to create instance of NSTimer class:
NSTimer *countUpTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:#selector(countUpTimerFired:) userInfo:nil repeats:YES];
Here is the method for event handling:
- (void)countUpTimerFired:(id)sender {
dispatch_async(dispatch_get_main_queue(), ^{
recordStatusLabel.text = [dateFormatter stringFromDate:[NSDate date]];
});
}
Keep of dateFormatter and countUpTimer as class variables.
This is the simple approach to achieve your required functionality as you are starting your time from current device time; So you won't be requiring extra efforts to get value from label, incrementing the value and then converting back to string.
EDIT:
If you want to start the counter from anyother time value or from a string, you can keep a integer variable to keep the value of time in seconds. Then increment the value when timer event gets called (every second) and then converting that integer to time string.
Here's the code for initial value:
NSString *timeString = recordStatusLabel.text; //contains a string in time format like #"2:16:23" or #"00:00:00" or current time or any other value.
NSArray *timeComponents = [timeString componentsSeparatedByString:#":"];
int timeInSeconds = [timeComponents[0] intValue]*3600 + [timeComponents[1] intValue]*60 + [timeComponents[2] intValue];
in the event handling of timer:
- (void)countUpTimerFired:(id)sender {
timeInSeconds++;
int hours = timeInSeconds/3600;
int minutes = (timeInSeconds%3600)/60;
int seconds = timeInSeconds%60;
dispatch_async(dispatch_get_main_queue(), ^{
[recordStatusLabel setText:[NSString stringWithFormat:#"%d:%02d:%02d", hours, minutes, seconds]];
});
}
Since you are dealing with a timer which the fastest value is the second, then to achieve performance you just fire a timer which repeats every second.
Declare your instance variables
#implementation Yourclass {
NSDate *startDate;
NSTimer *yourTimer;
NSString *myTime;
}
When you click a button to start timer
-(IBAction)startTimer:(id)sender {
startDate = [NSDate date];
yourTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timeHandler:) userInfo:nil repeats:YES];
[yourTimer fire];
}
Implement your method which is the handler method of timer
-(void)timeHandler:(NSTimer *)myTimer {
//Difference between dates in seconds
NSTimeInterval elapsedTime = [startDate timeIntervalSinceNow];
//Divide by 3600 to get the hours
NSInteger hours = elapsedTime/3600;
//Divide by 60 to get the minutes
NSInteger minutes = elapsedTime/60;
NSInteger seconds = elapsedTime;
myTime = [NSString stringWithFormat:#"%i:%i:%i",hours, minutes, seconds];
// update the label
recordStatusLabel.text = myTime;
}
I have a foursquare hours array (Foursquare API) that stores segments of hours when a specific venue is open. It looks something like this:
[{
"days":[1,2,3,4,7],
"includesToday":true,
"open":[
{"end":"+0200","start":"1000"}],
"segments":[]},
{
"days":[5,6]
,"open":[
{"end":"+0300","start":"1000"}],
"segments":[]}
]
How do I find out if the venue is opened or closed at current time?
I handle it like this: 4sq hours API gist
-(NSDictionary*)isVenueOpenDictionaryForHours:(NSArray*)hours{
// defaults and inits
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDictionary *lastSegmentYesterday = [[NSDictionary alloc] init];
NSDate *dateNow = [NSDate date];
NSString *venueOpenText = [[NSString alloc] init];
NSString *venueOpen = #"no";
// get components for today
NSDateComponents *compsNow = [gregorian components:NSWeekdayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSDayCalendarUnit|NSMonthCalendarUnit|NSYearCalendarUnit fromDate:dateNow];
// get weekday for today and yesterday so we can lookup 4sq API
NSInteger weekday = [compsNow weekday];
NSInteger weekdayYesterday = (weekday>1)?weekday-1:7;
// look for todays' segment
NSMutableArray *venueOpenSegments = [[NSMutableArray alloc] init]; // stores all the segments when the venue is open
for (NSDictionary *segment in hours){
// get today's segment (if it exists)
if ([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekday]]){
for (NSDictionary *dictOpen in segment[#"open"])
[venueOpenSegments insertObject:#{#"end": [dictOpen[#"end"] mutableCopy], #"start":[dictOpen[#"start"] mutableCopy]}.mutableCopy atIndex:venueOpenSegments.count];
}
// check the day before if the venue is open past midnight
if (([segment[#"days"] containsObject:[NSNumber numberWithInteger:weekdayYesterday]] && [segment[#"open"] count])){
// get the last segment (that should be the one passing midnight)
NSDictionary *tempSegment = [segment[#"open"] lastObject];
// if it has more than 4 characters it's after midnight ("+02:00"), also, ignore if it closes at midnight
if ([tempSegment[#"end"] length] > 4 && ![tempSegment[#"end"]isEqualToString:#"+0000"]){
// create a new segment that starts at midnight and lasts till the time it closes (early AMs usually)
lastSegmentYesterday = #{#"start":#"0000", #"end":[tempSegment[#"end"] substringFromIndex:1]};
}
}
}
// add last night segment that passes midnight as the first segment of today
if (lastSegmentYesterday.count){
[venueOpenSegments insertObject:lastSegmentYesterday atIndex:0];
}
// go through all the segments and find out if the venue is closed or open
if (venueOpenSegments.count){
NSDateComponents *comps = [[NSDateComponents alloc] init];
NSDateFormatter *timeFormatter = [[NSDateFormatter alloc]init];
timeFormatter.dateFormat = #"HH:mm"; // set time output format
int segmentNumber = 0;
for (NSMutableDictionary *segment in venueOpenSegments){
segmentNumber++;
// confing start date
[comps setDay:compsNow.day];
[comps setMonth:compsNow.month];
[comps setYear:compsNow.year];
[comps setHour:[[segment[#"start"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"start"] substringFromIndex:2] intValue]];
NSDate *dateStart = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// config end date
// check if the segment goes to next day
BOOL closesTomorrow = NO;
if ( [segment[#"end"] length]==5 ){
segment[#"end"] = [segment[#"end"] substringFromIndex:1];
closesTomorrow = YES;
}
[comps setHour:[[segment[#"end"] substringToIndex:2] intValue]];
[comps setMinute:[[segment[#"end"] substringFromIndex:2] intValue]];
NSDate *dateEnd = [[[NSCalendar currentCalendar] dateFromComponents:comps] copy];
// add a day if it closes tomorrow
if (closesTomorrow){
NSDateComponents *nextDayComponent = [[NSDateComponents alloc] init];
nextDayComponent.day = 1;
dateEnd = [gregorian dateByAddingComponents:nextDayComponent toDate:dateEnd options:0];
}
// start checking if it's open or closed
// now < segment start
if ([dateNow compare:dateStart] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"opens at %#",[timeFormatter stringFromDate: dateStart]];
venueOpen = #"later";
break;
}
// segment end < now
else if ([dateEnd compare:dateNow] == NSOrderedAscending){
if (segmentNumber == venueOpenSegments.count){
venueOpenText = [NSString stringWithFormat:#"closed since %#",[timeFormatter stringFromDate: dateEnd]];
break;
}
continue;
}
// segment start < now < segment end
else if ([dateStart compare:dateNow] == NSOrderedAscending && [dateNow compare:dateEnd] == NSOrderedAscending){
venueOpenText = [NSString stringWithFormat:#"open till %#",[timeFormatter stringFromDate: dateEnd]];
venueOpen = #"yes";
break;
}
// rare but possible... last minute of the venue being open (I treat it as closed)
else {
venueOpenText = #"closing right now";
}
}
}
else venueOpen = #"closed today"; // no segments for today, so it's closed for the dayƦ
// return results
return #{#"open":venueOpen, #"string":venueOpenText};
}
and I update my UILabel like this:
NSDictionary *venueOpen = [self isVenueOpenDictionaryForHours:_arrayVenues[indexPath.row][#"hours"]];
label.text = venueOpen[#"string"];
if ([venueOpen[#"open"] isEqualToString:#"no"]){
label.textColor = [UIColor colorWithHexString:#"b91d47" alpha:1]; // red
} else if ([venueOpen[#"open"] isEqualToString:#"yes"]) {
label.textColor = [UIColor colorWithHexString:#"1e7145" alpha:1]; // green
} else if ([venueOpen[#"open"] isEqualToString:#"later"]) {
label.textColor = [UIColor colorWithHexString:#"e3a21a" alpha:1]; // yellow
}
BTW, I use pod 'HexColors' for colorWithHexString methods
I have a countdown timer from the current date until 10 minutes have passed.
It counts down completely fine when I'm on the view, but when I change views and come back, then the timer stops, could anybody point me in the right direction?
-(void)updateCountdown {
dateFormatter = [[NSDateFormatter alloc]init];
[dateFormatter setDateFormat:#"YYYY-MM-dd-HH-mm-ss"];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSUInteger unitFlags = NSMinuteCalendarUnit|NSSecondCalendarUnit;
NSDateComponents *dateComponants = [calendar components:unitFlags fromDate:startingDate toDate:endingDate options:0];
NSInteger minutes = [dateComponants minute];
NSInteger seconds = [dateComponants second];
NSString *countdownText = [NSString stringWithFormat:#"Free Check: %ld:%ld", (long)minutes, (long)seconds];
timeLabel.text = countdownText;
NSLog (#"Startdate is %#", startingDate);
NSLog(#"Enddate is %#", endingDate);
//Attempt at saving the time but I guess this only saves the text?
NSString * saveStringTime = timeLabel.text;
NSUserDefaults * defaultsTime = [NSUserDefaults standardUserDefaults];
[defaultsTime setObject:saveStringTime forKey:#"saveStringTime"];
[defaultsTime synchronize];
[self performSelector:#selector(updateCountdown) withObject:nil afterDelay:1];
}
I think I need something in my ViewDidLoad to get whatever time it was and put that back in?
Thank you
You have to get the "savedStringTime" in viewWillAppear to resume from the "savedTime". Since viewDidLoad will get invoked only if the view is loaded to memory, viewDidLoad will not get invoked when the user navigates back to the same view(since view is still in memory).
To get proper working, Invoke your method to save the time in "viewWillDisappear" and in "viewWillAppear" check if the "savedStringTime" is available, if yes then resume it or else start a fresh counter.