Invalidating instanced NSTimers - ios

I have a UIScrollView which is being populated by UILabels which are being updated by NSTimers on each page which is being populated by an NSDictionary
for (id key in _infoDict) {
NSTimer *releaseDateTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
selector: #selector(calculateReleaseDate:) userInfo: dateInfo repeats: YES];
[self.releaseDateTimer fire];
}
The UILabels which are being refreshed are all referenced by their tags which are based on which page they are on and which calendar unit.
-(void)calculateReleaseDate:(NSTimer*)theTimer {
NSString *monthString = [[NSString alloc]initWithFormat:#"%i",[finalDate month]];
NSString *dayString = [[NSString alloc]initWithFormat:#"%i",[finalDate day]];
NSString *hourString = [[NSString alloc]initWithFormat:#"%i",[finalDate hour]];
NSString *minutesString = [[NSString alloc]initWithFormat:#"%i",[finalDate minute]];
NSString *secondsString = [[NSString alloc]initWithFormat:#"%i",[finalDate second]];
UILabel *monthValue = (UILabel*)[self.scrollView viewWithTag:[monthTag intValue]];
[monthValue setText:monthString];
UILabel *dayValue = (UILabel*)[self.scrollView viewWithTag:[dayTag intValue]];
[dayValue setText:dayString];
UILabel *hourValue = (UILabel*)[self.scrollView viewWithTag:[hourTag intValue]];
[hourValue setText:hourString];
UILabel *minValue = (UILabel*)[self.scrollView viewWithTag:[minTag intValue]];
[minValue setText:minutesString];
UILabel *secValue = (UILabel*)[self.scrollView viewWithTag:[secTag intValue]];
[secValue setText:secondsString];
}
This isn't the complete method but you get the idea.
Now at some point I want to rebuild the entire page by removing all the UIScrollView subviews and then repopulating it.
This is where my problem is coming in is that my labels values are jumping between numbers.
I can only assume that there are still instances of my previously created timers are still accessing these labels as well.
Is there a way to access the timers on the runloop and running [NSTimer invalidate]; on them or am I going about this all wrong.
Any input would be appreciated.

You can add your NSTimer instances in a NSMutableArray in your for loop. Then, you can invalidate them if they are still valid in another for loop. You can use isValid method to check if NSTimer is still valid.

Related

Method not working on second time through: SVPullToRefresh

So, I've made some progress on my issue described here — https://stackoverflow.com/questions/27348458/svpulltorefresh-infinite-scroll-with-an-rss-feed
Nonetheless, I have a problem. The table loads 10 YouTube videos initially. Then, the second ten will load using
__weak MasterViewController *weakSelf = self;
[self.tableView addInfiniteScrollingWithActionHandler:^{
NSLog(#"Infinite Scroll");
[weakSelf updateVideoList];
}];
-(void) updateVideoList essentially adds 10 items to the array that loads into the table view (reloadData is called on the tableview at the end after addObjectsFromArray adds items to the initial array). This works well enough. The problem is that trying to load a third 10 items does not work. I added the NSLog to see if it even goes into the method a second time, and it doesn't.
Is there any reason for the method to not work a second time?
Edit Here's updateVideoList, but I used the log to determine that the method isn't even called the second time through:
- (void) updateVideoList {
NSString *baseDomain = #"https://gdata.youtube.com/feeds/api/videos";
NSString *maxresults = #"10";
NSString *startIndex = [NSString stringWithFormat:#"%lu", (unsigned long)videoList.count+1];
NSString *orderBy = #"published";
NSString *author = #"theverge";
NSString *extension = #"v=2&alt=jsonc";
NSString *urlYoutube = [NSString stringWithFormat:#"%#?max-results=%#&start-index=%#&orderby=%#&author=%#&%#",
baseDomain,maxresults, startIndex,orderBy,author,extension];
NSDictionary *listOfVideos = [JSONParser listOfVideos:urlYoutube];
int videoListSize = [[[listOfVideos valueForKey:#"data"] valueForKey:#"totalItems"] intValue];
if (videoListSize>0) {
NSMutableArray *secondYoutubeList = [[NSMutableArray alloc] init];
NSArray *arrayVideoList = [[listOfVideos valueForKey:#"data"] valueForKey:#"items"];
for (NSDictionary *videoDictionary in arrayVideoList) {
NSString *idVideo = [videoDictionary valueForKey:#"id"];
NSString *description = [videoDictionary valueForKey:#"description"];
//NSString *updated = [videoDictionary valueForKey:#"updated"];
// NSString *departTimeDate = [videoDictionary valueForKey:#"updated"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
//NSDate *date = [dateFormatter dateFromString:departTimeDate];
NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];
NSString *updated = [NSString stringWithFormat:#"%ld, %ld, %ld", (long)month,(long)day,(long)year];
NSString *duration = [videoDictionary valueForKey:#"duration"];
NSString *title = [videoDictionary valueForKey:#"title"];
// If we are using wifi, take hqDefault
NSString *thumbnail = [[videoDictionary valueForKey:#"thumbnail"] valueForKey:#"hqDefault"];
YoutubeVideo *video = [[YoutubeVideo alloc] initWithId:idVideo withDescription:description withUpdated:updated withDuration:duration withTitle:title withThumbnail:thumbnail];
[secondYoutubeList addObject:video];
}
[videoList addObjectsFromArray:secondYoutubeList];
[self.tableView reloadData];
}
}
You should call
[self.tableView.infiniteScrollingView stopAnimating];
Once your work of reloading is got completed.

Display Text Word by Word on iOS

(I recognize that this is a duplicate of another question of mine, but that was put on hold, and when I reworked the question I was instructed to post it as a new question as none of the answers there could solve my request.)
I have a request that I think should be fairly easy to accomplish, but I cannot find the solution anywhere.
Say I have a string with about a paragraph or so of text. I want to display that text on a UITextView, but in a special way. I want the words to print out one by one, kind of like how you see in the movies on old computer terminals.
I do not have trouble splitting the string, that is OK, but the trouble is I cannot print out the individuals strings to my textView one by one. The UItextView is called self.textView. My code is as follows:
for (int wordIndex = 0; wordIndex < [wordList count]; wordIndex++) {
//[self.textView setText: [NSString stringWithFormat:#"%# %#",self.textView.text, [wordList objectAtIndex:wordIndex]]];
NSString *newText = [NSString stringWithFormat:#"%#", [wordList objectAtIndex:wordIndex]];
fullString = [NSString stringWithFormat:#"%# %#",fullString, newText];
[self.textView performSelector:#selector(setText:) withObject:fullString afterDelay:.9];
}
Where fullString is a global variable. Wordlist is an NSArray of my words (as strings).
If you need further clarification, then please do not hesitate to ask. Thanks so much!
Rather than trying to do everything in a loop, I suggest setting up a repeating NSTimer and adding a new word to your text view each time it fires. That gives you better control of the timing of updates and gives the UI a chance to display the result before the next iteration. When the last word has been shown, invalidate the timer.
Using answers from #Phillip Mills and #Ashish Kakkad I have figured this out. Here is my code (I cannot get to to format correctly, so if someone could edit it for me that would be awesome):
- (void) startTimer {
if ( self.wordtimer == nil ) {
self.wordtimer = [NSTimer scheduledTimerWithTimeInterval: 0.1
target: self
selector: #selector(printWords)
userInfo: nil
repeats: YES];
}
}
- (void) stopTimer {
if ( self.wordtimer || [self.wordtimer isValid])
{
[self.wordtimer invalidate];
self.wordtimer = nil;
}
}
-(void)printWords {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
Element *tempElement = [[Element alloc] init];
tempElement = [self.elementsArray objectAtIndex:appDelegate.currentElement];
NSArray *wordList = [tempElement.text componentsSeparatedByString:#" "];
if(wordIndex < [wordList count]){
[self.textView setText: [NSString stringWithFormat:#"%# %#",self.textView.text, [wordList objectAtIndex:wordIndex]]];
wordIndex++;
[self resetTextAndFont];
[self.textView setFont:[UIFont fontWithName:#"HelveticaNeue-Medium" size:15]];
NSLog(#"Timer Fired!");
}
else {
//[self.wordtimer invalidate];
[self stopTimer];
NSLog(#"Timer quit.");
[self resetTextAndFont];
[self.textView setFont:[UIFont fontWithName:#"HelveticaNeue-Medium" size:15]];
wordIndex = 0;
}
}
Whenever I want to fire the animation I just clear the textView's text and call [self startTimer] and to invalidate it I just call [self stopTimer]. Wordindex is a global integer.
For displaying the word by word, you need to first split the sentence in a space character using componentsSeparatedByCharactersInSet and it will return an array then the same you need to extract in a delay manner after that you can append the string in your UITexyView which can be done by using NSTimer and like that below:-
//Below for delaying use the timer
- (void)viewDidLoad
{
[super viewDidLoad];
timer=[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(startTimer) userInfo:nil repeats:YES];
}
//Below is the timer method
-(void)startTimer
{
//Assuming some string
NSString *str=#"This is testing";
//Splitting the sentence by space character
NSArray *arr=[str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
//len is integer which is a counter for iteration of array.
if (len<arr.count)
{
//Appending word by word
[[self.textView textStorage] appendAttributedString:[[NSAttributedString alloc]initWithString:arr[len]]];
[[self.textView textStorage] appendAttributedString:[[NSAttributedString alloc]initWithString:#" "]];
len++;
}
else
{
[timer invalidate];
return;
}
}
Take global to class string and int variable say string is fullString and int variable is wordIndex. Use NSTimer or delay to invoke custom method. In that method put check like
if(wordIndex < [wordList count]){
NSString *newText = [NSString stringWithFormat:#"%#", [wordList objectAtIndex:wordIndex]];
// increment the global int variable by one i.e.wordIndex++
// append this string to exsting string
// set appended text to textview...
}

UIButton Title not updating all the time

I am programmatically changing the title of a UIButton to display count of items in a NSMutableArray.
Sometimes, the title does not update as I add items to the array. When I do the NSLog of what the title should be, it is correct but the button title does not update all the time.
Can anyone spot a problem with my code?
- (void) refreshAfterBlueToothScan
{
/*************************************Refresh Displayed Total********************/
//[_manualBCtemporaryCartArray26 removeAllObjects];
//_manualBCtemporaryCartArray26 = [[NSMutableArray alloc] init];
[localTempArray removeAllObjects];
if (!localTempArray)
{
localTempArray = [[NSMutableArray alloc] init];
}
// Get the DBAccess object;
DBAccess *dbAccess1 = [[DBAccess alloc] init];
// Get the products array from the database
//Get the latest NSMutableArray
localTempArray = [dbAccess1 getProductsFromTmpSales];
// Close the database because we are finished with it
[dbAccess1 closeDatabase];
//Count How many products in array
int iNumberofArrayProducts;
iNumberofArrayProducts = [localTempArray count];
NSString* productCount = [NSString stringWithFormat:#"%i", iNumberofArrayProducts];
NSLog(#"The NUMBER OF TmpTable PRODUCTS is %#",productCount);
//Sum the selling price of the records in the _manualBCtemporaryCartArray26
amountSum = [localTempArray valueForKeyPath:#"#sum.lTotalSellingPrice"];
NSLog(#"The total TmpTable SELLING PRICE is %#",amountSum);
NSNumberFormatter * formatter = [[NSNumberFormatter alloc]init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMaximumFractionDigits:2]; // Set this if you need 2 digits
[formatter setMinimumFractionDigits:2]; // Set this if you need 2 digits
NSString * newString = [formatter stringFromNumber:[NSNumber numberWithFloat:[amountSum floatValue]]];
NSLog(#"FORMATTED MONTHLY SALES IS,R%#",newString);
//This is a hack that replaces the comma with a dot. I want to display the Price as R2 000.10
NSCharacterSet *doNotWant = [NSCharacterSet characterSetWithCharactersInString:#"/:,"];
newString=[[newString componentsSeparatedByCharactersInSet: doNotWant] componentsJoinedByString: #"."];
NSString *item = [NSString stringWithFormat:#"R%# (%i)",newString, iNumberofArrayProducts];
/**********Play audio to warn user that item was added to cart*******/
// ivar
SystemSoundID mBeep;
// Create the sound ID
NSString* path = [[NSBundle mainBundle]
pathForResource:#"scanBarCode" ofType:#"mp3"];
NSURL* url = [NSURL fileURLWithPath:path];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &mBeep);
// Play the sound
AudioServicesPlaySystemSound(mBeep);
// Dispose of the sound
//AudioServicesDisposeSystemSoundID(mBeep);
/**********END - Play audio to warn user that item was added to cart*******/
//When user scans the barcode, we want to display the product that was just scanned. This allows user to see what product they just scanned as it is automatically loaded on the shopping cart. Put IF Statement so this code only executes after the scanning barcode.
[self.searchDisplayController setActive: YES animated: YES];
//self.searchDisplayController.searchBar.hidden = NO;
self.searchDisplayController.searchBar.text = [NSString stringWithFormat:#"%i",fklInventoryID];
[self.searchDisplayController.searchBar becomeFirstResponder];
[self.searchDisplayController.searchBar resignFirstResponder];
NSLog(#"BUTTON ITEM IS,%#",item);
[manualTotalPriceBtn setTitle: item forState: UIControlStateNormal];
/*************************************END - Refresh Displayed Total********************/
}
Try this:
[manualTotalPriceBtn setAttributedTitle:nil forState:UIControlStateNormal];
[manualTotalPriceBtn setTitle:item forState:UIControlStateNormal];
If you are using a xib file the IB will set your value for the attributedTitle instead of the title.
try changing button type from system to custom.
Check that you update the button on the mainthread. If your method is called from a callback of whatever bluetooth scanner module you are using, it might be happening on a backround thread. This will cause button to sometimes update, sometimes it will not, or later.
If not, check the superview property of the button to not be nil. Maybe somewhere in your code you create a bew button, add it to the superview but forgot update your property to point to it (or some other scenario where you update a button that is not inserted in the view).

Create Events for MBCalendarKit in iOS

I imported MBCalendar Kit into my project, and I don't know how to add an event or array of events in calendar. I found this code:
NSMutableDictionary *eventsDict = [[NSMutableDictionary alloc] init];
for (int i =0; i< eventsArray.count ;i++)
{
// Create events
eventsDict = eventsArray[i];
CKCalendarEvent* aCKCalendarEvent = [[CKCalendarEvent alloc] init];
aCKCalendarEvent.title = [eventsDict objectForKey:#"email"];
aCKCalendarEvent.date = date; //[eventsArray objectForKey:#"phone"];
aCKCalendarEvent.address = [eventsDict objectForKey:#"addrLine1"];
aCKCalendarEvent.image = [eventsDict objectForKey:#"pPic"];
aCKCalendarEvent.name = [eventsDict objectForKey:#"fname"];
aCKCalendarEvent.appDate = [eventsDict objectForKey:#"apntDt"];
aCKCalendarEvent.notes = [eventsDict objectForKey:#"notes"];
aCKCalendarEvent.phone = [eventsDict objectForKey:#"phone"];
[myeventsArray addObject: aCKCalendarEvent];
}
[_data setObject:myeventsArray forKey:date];
but I don't know where to write it, or how to use it. Can anyone help me?
Thank you.
I'm working with this Framework and I've had the same issues.
What worked for me was to use the NSDate+Components category, specifically the dayWithDay:month:year method to create the dates for the events, then create as many events as you want the way you're doing it, encapsulate all the events that are on the same day in an array and lastly setting that array as an object for the NSDictionary data with the previously created as the key to that array. Here's an example:
NSDate *eventDate1 = [NSDate dateWithDay:8 month:8 year:2014];
NSDate *eventDate2 = [NSDate dateWithDay:9 month:8 year:2014];
CKCalendarEvent *event1 = [CKCalendarEvent eventWithTitle:#"Event 1" andDate:eventDate1 andInfo:nil];
CKCalendarEvent *event2 = [CKCalendarEvent eventWithTitle:#"Event 2" andDate:eventDate2 andInfo:nil];
NSArray *today = [NSArray arrayWithObjects:event1, nil];
NSArray *tomorrow = [NSArray arrayWithObjects:event2, nil];
[[self data] setObject:today forKey:eventDate1];
[[self data] setObject:tomorrow forKey:eventDate2];
Hope this helps :D
I'm working on my own framework based on this but with an iOS7 native feel, it's not finished yet but here is the repo:
https://github.com/AndoniV/CalendarBar_iOS7_Style.git

Objective C, what [NSString initWithFormat] exactly does?

I just would like to know the difference between the line 1 and 2 bellow:
_subtitle = #"Test"; //Line 1
_subtitle = [NSString stringWithFormat: #"Test"]; //Line 2
If I asked that question, it is because I got a problem by using MKAnnotation. In the method bellow, I try to update the subtitle delegate property of MKAnnotation (which is nonatomic, copy and readonly). But it's look like that I got a zombie when using the line 2 and nothing when using the line 1. So my question is why?
- (void) initCoordinateWithAddress:(NSString*)address;
{
self.address = address;
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString: address completionHandler:^(NSArray *placemarks,NSError *error)
{
CLPlacemark *place = [placemarks objectAtIndex:0];
_coordinate = place.location.coordinate;
_title = self.address;
_subtitle = #"Test"; //Line 1: don't crash
_subtitle = [NSString stringWithFormat: #"Test"]; //Line 2: crash
//_subtitle = [[NSString stringWithFormat: #"[%.2f,%.2f]", self.coordinate.latitude, self.coordinate.longitude] copy];
_isInit = YES;
[self.customDelegate didCalculateCoordinate: place.location.coordinate forAnnotation: self];
}];
}
I actually already fixed my problem by using the method copy, but I still not understand what is the difference between the line 1 and 2, if anyone can help me to understand what the difference is, I will appreciate.
Edit:
1- I am not using ARC
2- _subtitle comes form #synthesize subtitle = _subtitle; And subtitle is a part of the MKAnnotation protocol, with the property nonatomic, readonly and copy
Regards,
Cyril
If you are not using ARC, the answer is straightforward and is what Anoop Vaida wrote. However, I think some further explanation is needed.
This line
_subtitle = #"Test";
Creates a reference to a string literal. If you take a peak at its retain count in the current implementation of foundation, you'll find it is a very large number (NSIntegerMax I think). If the code for -release and -retain comes across this value for the retain count, they do not decrement or increment it. Thus string literals have infinite life times.
This line:
_subtitle = [NSString stringWithFormat: #"Test"];
creates a string you do not own. Unless you take steps to claim ownership, it could disappear at any time, most likely when the autorelease pool is drained. Your options are create a string you do own
_subtitle = [[NSString alloc] initWithFormat: #"Test"];
or to retain it.
_subtitle = [NSString stringWithFormat: #"Test"];
[_subtitle retain]; // Can be combined with the previous line if you like.
or to copy it
_subtitle = [[NSString stringWithFormat: #"Test"] copy];
Note that, in all cases, you need to release the previous value of _subtitle before you overwrite it, or you will get a leak e.g.
[_subtitle release];
_subtitle = [[NSString alloc] initWithFormat: #"Test"];
This is why it is better to have a property. Just because the MKAnnotation subtitle property is read only, does not mean you can't override it with your own read/write property. e.g.
#interface MyAnnotation : NSObject <MKAnnotation>
// other stuff
#property (readwrite, copy, nonatomic) NSString* subtitle;
#end
If you then synthesize it, you'll get all the correct memory management code and you can just do
[self setSubtitle: [NSString stringWithFormat: #"test"]];
or, if you must use dot notation
self.subtitle = [NSString stringWithFormat: #"test"];
I just would like to know the difference between the line 1 and 2
bellow:
_subtitle = #"Test"; //Line 1
_subtitle = [NSString stringWithFormat: #"Test"]; //Line 2
If you ask just the above these both are same.
While checking your code, the difference is quite visible.
You are creating a new autoreleased subtitle which is getting released once the block is over.
I don't think your solution is to understand how the string initializes work, but more on how blocks deal with variables.
When I think about it I think you may want to try an access _subtitle by it's property and not it's ivar.
self.subtitle
This should increment the retain count and keep everything functioning fine.
If you look up the documentation for initWithFormat: it links you to Formatting String Objects with many examples.
This basically allows (s)printf-style format strings, like so:
NSString *string1 = [[NSString alloc] initWithFormat:#"A string: %#, a float: %1.2f",
#"string", 31415.9265];
// string1 is "A string: string, a float: 31415.93"
The key is to specify arguments by adding a , after the string argument.
The '[NSString stringWithFormat:]' allows you to add a variable into the string, for example:
int myVar = 3;
NSString *myString = [NSString stringWithFormat:#"This number is %i", myVar];
The resulting string would be (if you were to NSLog it, for example): "This number is 3"
However you cannot do it like this:
NSString *myString = #"This number is %i", myVar;
Which would present you with an error.

Resources