I'm pretty new to this, so bear with me.
I built an app using iOS Programming: The Big Nerd Ranch Guide 4th ed called Homepwner which utilizes archiving to store information when the app goes into the background and load information when it starts up again, and it works totally fine. I decided to implement the same strategy for my own app called SBL, but when the app is loaded after having been quit, the data appears to load but doesn't display properly.
The app displays an array of "CCIEvent"s in a table and they appear like the example:
7:00 AM: Wet and Dirty Diaper
8:48 AM: Fed - Both - 20 min
9:48 AM: Napped - 1 hr 0 min
But when I quit the app and start it again, the list pulls up like this (I had to identify it as code to make this post, but the info below displays on screen in my table as such):
<CCIEvent: 0x154e44e20>
<CCIEvent: 0x154e2c7b0>
<CCIEvent: 0x154e77b90>
I really don't know where to look, but here's all the code relevant to saving/loading (I think):
In CCIEvent.m
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.startTime forKey:#"startTime"];
[aCoder encodeObject:self.bedTime forKey:#"bedTime"];
[aCoder encodeObject:self.wakeTime forKey:#"wakeTime"];
[aCoder encodeBool:self.isNap forKey:#"isNap"];
[aCoder encodeInt:self.feedDuration forKey:#"feedDuration"];
[aCoder encodeInt:self.sourceIndex forKey:#"sourceIndex"];
[aCoder encodeInt:self.diaperIndex forKey:#"diaperIndex"];
[aCoder encodeObject:self.note forKey:#"note"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
_startTime = [aDecoder decodeObjectForKey:#"startTime"];
_bedTime = [aDecoder decodeObjectForKey:#"bedTime"];
_wakeTime = [aDecoder decodeObjectForKey:#"wakeTime"];
_isNap = [aDecoder decodeBoolForKey:#"isNap"];
_feedDuration = [aDecoder decodeIntForKey:#"feedDuration"];
_sourceIndex = [aDecoder decodeIntForKey:#"sourceIndex"];
_diaperIndex = [aDecoder decodeIntForKey:#"diaperIndex"];
_note = [aDecoder decodeObjectForKey:#"note"];
}
return self;
}
In CCIEventStore.m
- (instancetype)initPrivate
{
self = [super init];
if (self) {
NSString *path = [self eventArchivePath];
_privateEvents = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// If the array hadn't been saved previously, create an empty one
if (!_privateEvents) {
_privateEvents = [[NSMutableArray alloc] init];
NSLog(#"Did NOT load events");
} else {
NSLog(#"Loaded events");
}
}
return self;
}
- (CCIEvent *)createEventWithEventType:(NSString *)eventType
startTime:(NSDate *)startTime
bedTime:(NSDate *)bedTime
wakeTime:(NSDate *)wakeTime
isNap:(BOOL)isNap
feedDuration:(int)feedDuration
sourceIndex:(int)sourceIndex
diaperIndex:(int)diaperIndex
note:(NSString *)note
{
CCIEvent *event = [[CCIEvent alloc] initWithEventType:eventType
startTime:startTime
bedTime:bedTime
wakeTime:wakeTime
isNap:isNap
feedDuration:feedDuration
sourceIndex:sourceIndex
diaperIndex:diaperIndex
note:note];
[self.privateEvents addObject:event];
return event;
}
- (NSString *)eventArchivePath
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"events.archive"];
}
- (BOOL)saveChanges
{
NSString *path = [self eventArchivePath];
return [NSKeyedArchiver archiveRootObject:self.privateEvents
toFile:path];
}
In AppDelegate.m
- (void)applicationDidEnterBackground:(UIApplication *)application {
BOOL success = [[CCIEventStore sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all of the CCIEvents");
} else {
NSLog(#"Could not save any of the CCIEvents");
}
}
This is the some code from the view controller where the table is which I have called CCILogViewController.m:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterNoStyle];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"
forIndexPath:indexPath];
CCIEvent *event = self.sortedAndFilteredArray[indexPath.row];
if ([event.eventType isEqualToString:#"sleepEvent"] && event.wakeTime) {
NSDateComponents *dateComponentsToday = [[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger yearToday = [dateComponentsToday year];
NSInteger dayOfYearToday = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:[NSDate date]];
NSDateComponents *dateComponentsEvent = [[NSCalendar currentCalendar] components:NSCalendarUnitYear
fromDate:event.startTime];
NSInteger yearEvent = [dateComponentsEvent year];
NSInteger dayOfYearEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:event.startTime];
NSInteger dayOfYearWakeEvent;
dayOfYearWakeEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:event.wakeTime];
if (event.wakeTime && yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearWakeEvent == dayOfYearToday) {
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
} else if (event.wakeTime && yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1 && dayOfYearWakeEvent == dayOfYearToday) {
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
} else {
cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];
}
} else if (self.dateOptionIndex == 2 || self.dateOptionIndex == 3) {
cell.textLabel.text = [NSString stringWithFormat:#"%# %#", [dateFormatter stringFromDate:event.startTime], [self.sortedAndFilteredArray[indexPath.row] description]];
} else {
cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];
}
if (event.note) {
cell.accessoryType = UITableViewCellAccessoryDetailButton;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Here's how I get the sortedAndFilteredArray which is called in few different places including viewWillAppear:
- (void)sortAndFilterArray
{
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"startTime" ascending:self.arrayIsAscending];
NSArray *sortedArray = [[[CCIEventStore sharedStore] allEvents] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
NSMutableArray *sortedAndFilteredArray = [[NSMutableArray alloc] init];
NSMutableArray *finalArray = [[NSMutableArray alloc] init];
for (CCIEvent *event in sortedArray) {
switch (self.eventOptionIndex) {
case 1:
if ([event.eventType isEqualToString:#"sleepEvent"]) {
[sortedAndFilteredArray addObject:event];
}
break;
case 2:
if ([event.eventType isEqualToString:#"feedEvent"]) {
[sortedAndFilteredArray addObject:event];
}
break;
case 3:
if ([event.eventType isEqualToString:#"diaperEvent"]) {
[sortedAndFilteredArray addObject:event];
}
break;
default:
[sortedAndFilteredArray addObject:event];
break;
}
}
NSDateComponents *dateComponentsToday = [[NSCalendar currentCalendar] components:NSCalendarUnitYear fromDate:[NSDate date]];
NSInteger yearToday = [dateComponentsToday year];
NSInteger dayOfYearToday = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:[NSDate date]];
for (CCIEvent *event in sortedAndFilteredArray) {
NSDateComponents *dateComponentsEvent = [[NSCalendar currentCalendar] components:NSCalendarUnitYear
fromDate:event.startTime];
NSInteger yearEvent = [dateComponentsEvent year];
NSInteger dayOfYearEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:event.startTime];
NSInteger dayOfYearWakeEvent;
switch (self.dateOptionIndex) {
case 0:
dayOfYearWakeEvent = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay
inUnit:NSCalendarUnitYear
forDate:event.wakeTime];
// Filter here for dateIndex 0 (Today)
if ([event.eventType isEqualToString:#"sleepEvent"] && event.wakeTime && yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearWakeEvent == dayOfYearToday) {
[finalArray addObject:event];
} else if ([event.eventType isEqualToString:#"sleepEvent"] && event.wakeTime && yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1 && dayOfYearWakeEvent == dayOfYearToday) {
[finalArray addObject:event];
} else if (yearEvent == yearToday && dayOfYearEvent == dayOfYearToday) {
[finalArray addObject:event];
}
break;
case 1:
// Filter here for dateIndex 1 (Yesterday)
if (yearEvent == yearToday && dayOfYearEvent == dayOfYearToday - 1) {
[finalArray addObject:event];
} else if (yearEvent == yearToday - 1 && dayOfYearEvent == dayOfYearToday - 1 && dayOfYearEvent == 1) {
[finalArray addObject:event];
}
break;
case 2:
// Filter here for dateIndex 2 (Past Week)
if (yearEvent == yearToday && dayOfYearEvent >= dayOfYearEvent - 6) {
[finalArray addObject:event];
} else if (yearEvent == yearToday - 1 && dayOfYearEvent >= dayOfYearToday - 6 && dayOfYearEvent < 7) {
[finalArray addObject:event];
}
break;
default:
// No filter here for dateIndex 3 (All Time)
[finalArray addObject:event];
break;
}
}
self.sortedAndFilteredArray = [finalArray copy];
}
This is what is being called when getting description of a CCIEvent:
- (NSString *)description
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[dateFormatter setDateStyle:NSDateFormatterNoStyle];
if ([self.eventType isEqualToString:#"sleepEvent"]) {
NSString *bedTimeString = [dateFormatter stringFromDate:self.bedTime];
NSString *wakeTimeString = [dateFormatter stringFromDate:self.wakeTime];
long min = self.sleepDuration/60;
long hrs = self.sleepDuration/60/60;
long rMinutes = min - hrs * 60;
if (!self.wakeTime && self.bedTime) {
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Went Down for Nap", bedTimeString];
} else {
return [[NSString alloc] initWithFormat:#"%#: Went Down", bedTimeString];
}
} else if (self.wakeTime && !self.bedTime) {
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Woke from Nap", wakeTimeString];
} else {
return [[NSString alloc] initWithFormat:#"%#: Woke", wakeTimeString];
}
} else if (self.sleepDuration <= 60) {
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Napped - 1 min", bedTimeString];
} else {
return [[NSString alloc] initWithFormat:#"%#: Slept - 1 min", bedTimeString];
}
} else if (self.sleepDuration > 60 && self.sleepDuration < 60 * 60){
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Napped - %ld min", bedTimeString, min];
} else {
return [[NSString alloc] initWithFormat:#"%#: Slept - %ld min", bedTimeString, min];
}
} else if (hrs == 1) {
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Napped - 1 hr %ld min", bedTimeString, rMinutes];
} else {
return [[NSString alloc] initWithFormat:#"%#: Slept - 1 hr %ld min", bedTimeString, rMinutes];
}
} else {
if (self.isNap) {
return [[NSString alloc] initWithFormat:#"%#: Napped - %ld hrs %ld min", bedTimeString, hrs, rMinutes];
} else {
return [[NSString alloc] initWithFormat:#"%#: Slept - %ld hrs %ld min", bedTimeString, hrs, rMinutes];
}
}
} else if ([self.eventType isEqualToString:#"feedEvent"]) {
NSString *startTimeString = [dateFormatter stringFromDate:self.startTime];
NSArray *sourceArray = #[#"Both", #"Left", #"Right", #"Bottle"];
NSString *sourceString = sourceArray[self.sourceIndex];
return [[NSString alloc] initWithFormat:#"%#: Fed - %# - %d min", startTimeString, sourceString, self.feedDuration / 60];
} else if ([self.eventType isEqualToString:#"diaperEvent"]) {
NSString *startTimeString = [dateFormatter stringFromDate:self.startTime];
NSArray *diaperArray = #[#"Wet and Dirty", #"Wet", #"Dirty"];
NSString *diaperString = diaperArray[self.diaperIndex];
return [[NSString alloc] initWithFormat:#"%#: %# Diaper", startTimeString, diaperString];
}
return [super description];
}
Do you have a custom description method in CCIEvent? By default, NSObject description will display the memory location of an object which is exactly what you're seeing. You can do a basic test by creating something very simple like:
- (NSString *)description
{
return [[NSString alloc] initWithFormat:#"%#", self.note];
}
It still seems like something isn't loading somewhere, or I'd expect you to see that problem during general usage. If you do have a CCIEvent description method, can you post it? I'm also curious about sortedAndFilteredArray.
You might try sprinkling your code with NSLog() calls to see if you're getting what you expect at various points. Keep an eye on the XCode console when the app runs to see what data you're getting. Ie, in CCIEvent.m initWithCoder you might add something like the following at the end of the if (self) { block:
NSLog(#"Got %#", _note);
Or even check your description method right there:
NSLog([self description]);
Also be sure to do some NSLog's in your encodeWithCoder method. My suspicion is the problem is occurring on the save side - there's something keeping event details from saving properly, so when they are restored they are empty so in your cellForRowAtIndexPath method it's hitting the default condition and just calling description on CCIEvent, which I suspect may not exist and is doing the default behavior of displaying the memory location. If I'm right, this points to a disconnect between CCIEventStore's privateEvents and the event being edited in your detail view. Check the object properties in your header files and see if somewhere CCIEvent is being passed around using copy rather than strong.
Unrelated to the error, but still worth noting - the item in cellForRowAtIndexPath:
cell.textLabel.text = [self.sortedAndFilteredArray[indexPath.row] description];
Those lines probably aren't related to the issue, but they contain a redundant accessor. Since you already have:
CCIEvent *event = self.sortedAndFilteredArray[indexPath.row];
any times you want to access data from the event, you should use that variable, so the above textLabel would be:
cell.textLabel.text = [event description];
There are a few other places in cellForRowAtIndexPath that could use a similar modification.
Hope this helps some!
I sprinkled my code with NSLogs and determined that my CCIEvents were loading just fine. I found it odd that my special description worked before loading but not after loading. Before loading it would give me the string I wanted, but after loading it would point to memory. After inspecting my description method, I noticed that I defaulted to returning the super's description method unless the CCIEvent met certain criteria, which forced me to realize that my CCIEvents have an attribute of eventType which I did not include in the encodeWithCoder: or initWithCoder: blocks. All I had to do was add eventType in there, and my description works like it should. Thanks so much, Chris! I literally left this project alone for months because I was so stumped.
Related
We can use instruments for various kinds of analysis. But many programmers find this tool to be too complicated and too heavy to bring real value.
Is there a simple way to track all objects of a specific class, and for each to know who exactly was allocating them and to verify that they are being freed correctly?
The answer is yes! there is a way, and I'll demo it in my answer below
Tracking allocations easily:
How to use: you can put the 150 lines of code below into a file named AllocTracker.m and drag it into your project files.
Use the check box at the right pane of Xcode to enable/disable it in your compilation target.
What you'll get?
when enabled, this module will track all allocations and deallocations of UIImage objects and log them. (It can easily be modified for tracking other classes.)
In addition to logging every allocation and deallocation, it will periodically (currently every 15 seconds) dump all objects which are currently allocated, with some added info and the call stack which allocated them.
What is the added value?
This code was used in big projects to get rid of orphan objects which were left allocated without notice, allowing to significantly reduce the memory footprint of the app and fix memory leaks.
So here is the code for AllocTracker.m:
#define TRACK_ALLOCATIONS
#ifdef TRACK_ALLOCATIONS
#import <UIKit/UIKit.h>
#define TIMER_INTERVAL 15
#implementation UIApplication(utils)
+(NSString *)dateToTimestamp:(NSDate *)date
{
if (date == nil) {
date = [NSDate date];
}
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
}
NSString *ts = [dateFormatter stringFromDate:date];
return ts;
}
+(NSString*) getCaller:(int)stackDepth
{
#ifndef DEBUG
return #"NON DBG";
#else
NSArray *symbols = [NSThread callStackSymbols];
int lastIndex = (int)(symbols.count - 1);
if (lastIndex < 3) {
return #"NO DATA";
}
NSMutableString *result = [NSMutableString string];
int foundCount = 0;
for (int ix=3; ix <= lastIndex; ix++) {
NSString *line = symbols[ix];
NSRange rng1 = [line rangeOfString:#"["];
if (rng1.location == NSNotFound) {
continue;
}
NSRange rng2 = [line rangeOfString:#"]"];
NSString *caller = [line substringWithRange:NSMakeRange(rng1.location+1, rng2.location-rng1.location-1)];
if (foundCount > 0) { //not first
[result appendString:#"<--"];
}
[result appendString:caller];
if (++foundCount == stackDepth) {
break;
}
}
return (foundCount > 0) ? result : #"NO SYMBOL";
#endif
}
#end
#implementation UIImage(memoryTrack)
static NSMapTable *g_allocsMap;
static NSTimer *g_tmr;
static NSDate *g_lastDump = nil;
+(void)gotTimer:(NSTimer *)timer
{
[self dumpAllocs];
}
+(void)startTimer
{
static int count = 0;
g_tmr = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(gotTimer:) userInfo:#(count++) repeats:YES];
NSLog(#"starting timer %i", count);
}
+(void)cancelTimer
{
[g_tmr invalidate];
g_tmr = nil;
}
+(void)dumpAllocs
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
NSMutableString *str = [NSMutableString string];
[str appendString:#"\n#$# ========== Non-freed UIImages =========\n"];
NSMutableArray *sorted = [NSMutableArray array];
//make sure map is not changed while enumerating
static int s_ts_start = -1;
#synchronized (g_allocsMap) {
NSEnumerator *keysEnum = [g_allocsMap keyEnumerator];
UIImage *img;
while (img = [keysEnum nextObject]) {
NSString *value = [g_allocsMap objectForKey:img];
if (value) { //might be nulled because declared as weak
NSUInteger memUsed = CGImageGetHeight(img.CGImage) * CGImageGetBytesPerRow(img.CGImage);
NSString *objData = [NSString stringWithFormat:#"mem=%5ikb, size=%4ix%-4i", (int)(memUsed/1024), (int)img.size.width, (int)img.size.height];
NSString *line = [NSString stringWithFormat:#"%p - %# [%#]\n", img, objData, value];
if (s_ts_start<0) {
s_ts_start = (int)[line rangeOfString:#"["].location + 1;
}
if (line.length > (s_ts_start+10)) {
[sorted addObject:line];
}
}
}
}
if (sorted.count > 0) {
[sorted sortUsingComparator: ^NSComparisonResult(NSString *s1, NSString *s2)
{
//we expect '0x15a973700 - mem=3600kb, size=640x360 [16:14:27.5: UIIma...'
NSString *ts1 = [s1 substringWithRange:NSMakeRange(s_ts_start, 10)];
NSString *ts2 = [s2 substringWithRange:NSMakeRange(s_ts_start, 10)];
return [ts1 compare:ts2];
}];
int ix = 0;
for (NSString *line in sorted) {
[str appendFormat:#"#$# %3i) %#", ix++, line];
}
}
[str appendString:#"#$# ======================================================\n"];
NSLog(#"%#", str);
});
}
+(instancetype)alloc
{
NSString *caller = [UIApplication getCaller:4];
#synchronized (self) {
id obj = [super alloc];
NSLog(#"#$# UIImage alloc: [%p], caller=[%#]", obj, caller);
NSDate *now = [NSDate date];
NSString *value = [NSString stringWithFormat:#"%#: %#", [UIApplication dateToTimestamp:now], caller];
if (!g_allocsMap) {
g_allocsMap = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
}
[g_allocsMap setObject:value forKey:obj];
if (!g_lastDump) {
[self startTimer];
g_lastDump = now;
}
return obj;
}
}
-(void)dealloc
{
NSLog(#"#$# UIImage dealloc: [%#]", self);
}
#end
#endif //TRACK_ALLOCATIONS
How it works?
We create a category of UIImage and set our own version for alloc and dealloc. Every allocated object is saved into an NSMapTable object which works like a dictionary but allow storing object with weak pointers.
For convenience we were adding two methods under UIApplication which can be used by other modules if an appropriate header file is created. One method is for formatting the timestamp, and the other is for reading the call stack (only works in debug builds).
Tip for use:
if you use a real device and install idevicesyslog (brew install libimobiledevice), you can use the terminal to see all allocation debug, like this:
idevicesyslog | grep "#\$#"
I have the following array of data which I am fetching from SQLite3 database.
array (
{
amount = "$100";
balance = "$1505";
date = "06/22/2015";
id = 16;
note = Pay;
type = Pay;
},
{
amount = "$1000";
balance = "$1405";
date = "06/22/2015";
id = 15;
note = Pay;
type = Pay;
},
{
amount = "$200";
balance = "$405";
date = "06/22/2015";
id = 14;
note = Pay;
type = Pay;
},
{
amount = "$100";
balance = "$205";
date = "06/22/2015";
id = 13;
note = Pay;
type = Pay;
},
{
amount = "$100";
balance = "$105";
date = "06/22/2015";
id = 12;
note = Pay;
type = Pay;
},
{
amount = "$50";
balance = "$5,320.00";
date = "06/16/2015";
id = 11;
note = Pay;
type = Pay;
},
{
amount = "$50";
balance = "$5,270.00";
date = "06/09/2015";
id = 10;
note = Pay;
type = Pay;
},
{
amount = "$50";
balance = "$5220";
date = "06/02/2015";
id = 9;
note = Pay;
type = Pay;
},
{
amount = "$100";
balance = "$5170";
date = "06/03/2015";
id = 8;
note = Pay;
type = Pay;
},
{
amount = "$100";
balance = "$5070";
date = "06/02/2015";
id = 7;
note = Pay;
type = Pay;
},
{
amount = "$20";
balance = "$4970";
date = "05/29/2015";
id = 6;
note = water;
type = Deposit;
},
{
amount = "$100";
balance = "$4950";
date = "05/29/2015";
id = 5;
note = water;
type = Expense;
},
{
amount = "$50";
balance = "$5050";
date = "05/29/2015";
id = 4;
note = Pay;
type = Pay;
}
)
I want to make a tableview with sections on yearly basis and in rows each section data will show monthly basis after performing calculation of income and expense. For more info I am attaching a screen shot herewith.
I am attempting to solve the problem using the following method.
-(NSMutableArray*)arrangeSection:(NSMutableArray *)source
{
NSDateFormatter *_formatter=[[NSDateFormatter alloc]init];
[_formatter setLocale:[NSLocale currentLocale]];
[_formatter setDateFormat:#"YYYY"];
NSMutableArray *arrayMain=[NSMutableArray array];
for (int i=0; i<source.count; i++){
NSDictionary *dict=source[i];
NSDateFormatter *_formatterLocal=[[NSDateFormatter alloc]init];
[_formatterLocal setLocale:[NSLocale currentLocale]];
[_formatterLocal setDateFormat:#"MM/dd/yyyy"];
NSDate * date = [_formatterLocal dateFromString:[dict objectForKey:TABLE_DATE]];
NSString *yy=[_formatter stringFromDate:date];
NSMutableDictionary *secDict=[NSMutableDictionary dictionary];
NSMutableArray *secArray=[NSMutableArray array];
if (i==0){
[secDict setObject:yy forKey:#"Year"];
[secArray addObject:dict];
[secDict setObject:secArray forKey:#"Data"];
[arrayMain addObject:secDict];
}
else{
BOOL flg=NO;
for (NSDictionary *dict2 in arrayMain){
if([[dict2 objectForKey:#"Year"]isEqualToString:yy]){
flg=YES;
[[dict2 objectForKey:#"Data"]addObject:dict];
break;
}
}
if (!flg){
[secDict setObject:yy forKey:#"Year"];
[secArray addObject:dict];
[secDict setObject:secArray forKey:#"Data"];
[arrayMain addObject:secDict];
}
}
}
return arrayMain;
}
If I understand you requirement correct you want a section per year, each section with 12 rows, one per month.
This should not be too hard to achieve :).
I suggest that you make models of your JSON object:
{
amount = "$20";
balance = "$4970";
date = "05/29/2015";
id = 6;
note = water;
type = Deposit;
}
Let's call this class a "MonthModel", then create a "YearModel" holding 12 (or less, if no data is available for said year) "MonthModels". I am posting a suggestion for a structure of models in the untested code below.
Something like:
YearModel
#interface YearModel()
#property (strong, nonatomic) NSArray *monthModels;
#property (strong, assign) NSInteger year;
#end
#implementation YearModel
- (instancetype)initModelForYear:(NSInteger)year withMonthModels:(NSArray*)monthModels {
self = [super init];
if(self) {
self.year = year;
self.monthModels = monthModels;
}
return self;
}
#end
Your MonthModel
#interface MonthModel()
#property (strong, assign) NSFloat balance;
#property (strong, assign) NSFloat amount;
#end
#implementation MonthModel
- (instancetype)initWithBalance:(NSFloat)balance andAmount:(NSFloat)amount {
self = [super init];
if(self) {
self.balance = balance;
self.amount = amount;
}
return self;
}
#end
If your UIViewController is your UITableViewDataSource, then it can use an array of YearModels
#interface MyViewController() <UITableViewDataSource, UITableViewDelegate>
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSArray* yearModels;
#end
#implementation MyViewController
- (MonthModel*)monthModelForIndexPath:(NSIndexPath*)indexPath {
YearModel* yearModel = [yearModels objectAtIndex:indexPath.section];
MonthModel* monthModel = [yearModel.monthModels objectAtIndex:indexPath.row];
return monthModel;
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [yearModels count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
YearModel* yearModel = [yearModels objectAtIndex:section];
return [yearModel.monthModels count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"MonthCell";
MonthCell *cell = (MonthCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
MonthModel* monthModel = [self monthModelForIndexPath:indexPath];
[cell updateWithModel:monthModel];
return cell;
}
#end
Hopefully it will not be that tricky for you to somewhere in code create a method which reads your data from the SQLite and creates those models suggested above. And then set the "yearModels" property in the UIViewController.
However, please let me know if you need some inspiration the SQLite -> Model conversion.
Good luck!
I have solved my problem by the following method of sorting.
-(NSMutableArray*)arrangeSection:(NSMutableArray *)source
{
NSDateFormatter *_formatter=[[NSDateFormatter alloc]init];
[_formatter setLocale:[NSLocale currentLocale]];
[_formatter setDateFormat:#"YYYY"];
NSString *year = #"";
NSString *month = #"";
NSMutableArray *arrayMain=[[NSMutableArray alloc] init];
NSMutableDictionary *lastDict = [[NSMutableDictionary alloc] init];
NSMutableArray *lastArray = [[NSMutableArray alloc] init];
for (int i=0; i<source.count; i++){
NSDictionary *dict=source[i]; // Year data dictionary
NSDateFormatter *_formatterLocal=[[NSDateFormatter alloc]init];
[_formatterLocal setLocale:[NSLocale currentLocale]];
[_formatterLocal setDateFormat:#"MM/dd/yyyy"];
NSDate * date = [_formatterLocal dateFromString:[dict objectForKey:TABLE_DATE]];
[_formatter setDateFormat:#"YYYY"];
NSString *currentYear=[_formatter stringFromDate:date];
if (![year isEqualToString:currentYear]) {
if (i!=0) {
[lastDict setObject:year forKey:#"Year"];
[lastDict setObject:arrayMain forKey:#"Data"];
[lastArray addObject:lastDict];
lastDict = [[NSMutableDictionary alloc] init];
arrayMain=[[NSMutableArray alloc] init];
}
}
[_formatter setDateFormat:#"MMMM"];
NSString *currentMonth = [_formatter stringFromDate:date];
if (![month isEqualToString:currentMonth]) {
NSMutableDictionary *secDict=[[NSMutableDictionary alloc] init];
[secDict setObject:currentMonth forKey:TABLE_DATE];
if ([[dict valueForKey:TABLE_TYPE] isEqualToString:#"Expense"]) {
[secDict setObject:[dict valueForKey:TABLE_AMOUNT] forKey:TABLE_AMOUNT];
}
else
{
[secDict setObject:#"0" forKey:TABLE_AMOUNT];
}
if ([[dict valueForKey:TABLE_TYPE] isEqualToString:#"Deposit"])
{
[secDict setObject:[dict valueForKey:TABLE_AMOUNT] forKey:TABLE_NOTE];
}
else
{
[secDict setObject:#"0" forKey:TABLE_NOTE];
}
[secDict setObject:[dict valueForKey:TABLE_BALANCE] forKey:TABLE_BALANCE];
[arrayMain addObject:secDict];
}
else
{
NSMutableDictionary *previousOBJ= [arrayMain objectAtIndex:[arrayMain count]-1];
if ([[dict valueForKey:TABLE_TYPE] isEqualToString:#"Expense"])
{
[previousOBJ setValue:[NSString stringWithFormat:#"%d", [[previousOBJ valueForKey:TABLE_AMOUNT] integerValue] + [[dict valueForKey:TABLE_AMOUNT] integerValue]] forKey:TABLE_AMOUNT];
}
if ([[dict valueForKey:TABLE_TYPE] isEqualToString:#"Deposit"])
{
[previousOBJ setValue:[NSString stringWithFormat:#"%d", [[previousOBJ valueForKey:TABLE_NOTE] integerValue] + [[dict valueForKey:TABLE_AMOUNT] integerValue]] forKey:TABLE_NOTE];
}
[arrayMain replaceObjectAtIndex:[arrayMain count]-1 withObject:previousOBJ];
}
month = currentMonth;
year = currentYear;
if (i==source.count-1) {
[lastDict setObject:year forKey:#"Year"];
[lastDict setObject:arrayMain forKey:#"Data"];
[lastArray addObject:lastDict];
}
}
return lastArray;
}
I am creating an iOS app using Parse database(asynchronously) to store information that will be used when populating a mapview. I have been trying to figure out what is wrong for a long time and have done plenty of research without any luck. I have, however, found the source of the issue.
In my code, I am querying the parse database in hopes of getting the information I want and then storing the information in a custom pointAnnotation class, which is of type MkPointAnnotation. Each item is stored in an array of pointAnnotations, and once all items in the database have been stored in the array, the annotations are added to MyMapView. --I have tried adding the annotations as they are created, which does not change anything.
The issue I have been having is that randomly, the query will iterate under the for(PFObject *vendor in Vendors) and reach an error, calling NSLog(#"%#", error.debugDescription); which shows (null) in the output log. The amount of objects that return null seems to change each time I run the application, and occasionally it will work as expected. After adding a do while(pointArray.count < query.countObjects), the function will iterate roughly 20-30 times and then will add the correct number of annotations, however, it is extremely inefficient.
Is this an inefficiency within Parse or is there a better way to achieve the expected results?
PFQuery *query = [PFQuery queryWithClassName:#"Vendors"];
[query orderByDescending:#"updatedAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *vendors, NSError *error){
NSMutableArray *pointArray = [[NSMutableArray alloc] init];
if (!error) {
// The find succeeded.
// Do something with the found objects
do {
pointArray = [[NSMutableArray alloc] init];
for (PFObject *vendor in vendors) {
NSDate *lastUpdated = vendor.updatedAt;
NSDate *today = [NSDate date];
NSDate *newDate = [lastUpdated dateByAddingTimeInterval:86400];
if (today <= newDate) {
PFGeoPoint *point = vendor[#"Location"];
NSString *vendor_ID = vendor[#"Vendor_ID"];
NSMutableArray *FruitList = vendor[#"Fruits"];
NSMutableArray *VeggieList = vendor[#"Veggies"];
NSMutableArray *addressArray = vendor[#"Address"];
NSString *startHr = vendor[#"Start_Time"];
NSString *endHr = vendor[#"End_Time"];
Boolean more = false;
NSString *moreString = vendor[#"And_More"];
if ([moreString isEqual: #"true"]) {
more = true;
}
CLLocationCoordinate2D location;
location.latitude = point.latitude;
location.longitude = point.longitude;
pointAnnotation *newAnnotation = [[pointAnnotation alloc] init];
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"language"] isEqual:#"ENGLISH"]){
FindCartsLabel.text = #"Find Carts within:";
MilesTextField.text = #"Show All";
milesArray=[[NSArray alloc]initWithObjects:#"Show All", #"1 Mile", #"5 Miles", #"10 Miles", #"20 Miles", nil];
AddressBar.placeholder = ENGLISH_Address;
newAnnotation.title = #"Good. To. Go. Vendor";
newAnnotation.fruits = FruitList;
newAnnotation.veggies = VeggieList;
}else if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"language"] isEqual:#"SPANISH"]){
FindCartsLabel.text = #"Encuentra Carros Dentro:";
newAnnotation.title = #"Good. To. Go. Vendedor";
AddressBar.placeholder = SPANISH_Address;
NSMutableArray *spanishFruitList = [[NSMutableArray alloc]init];
for (NSString *current in FruitList) {
MilesTextField.text = #"Mostrar Todo";
milesArray=[[NSArray alloc]initWithObjects:#"Mostrar Todo", #"1 Milla", #"5 Millas", #"10 Millas", #"20 Millas", nil];
if ([current isEqual:#"Apples"]) {
[spanishFruitList addObject:SPANISH_Apples];
}
if ([current isEqual:#"Bananas"]) {
[spanishFruitList addObject:SPANISH_Bananas];
}
if ([current isEqual:#"Citrus"]) {
[spanishFruitList addObject:SPANISH_Citrus];
}
if ([current isEqual:#"Mangos"]) {
[spanishFruitList addObject:SPANISH_Mangos];
}
if ([current isEqual:#"Strawberries"]) {
[spanishFruitList addObject:SPANISH_Strawberries];
}
if ([current isEqual:#"And More"]) {
[spanishFruitList addObject:SPANISH_More];
}
}
NSMutableArray *spanishVeggieList = [[NSMutableArray alloc]init];
for (NSString *current in VeggieList) {
if ([current isEqual:#"Avocados"]) {
[spanishVeggieList addObject:SPANISH_Avocados];
}
if ([current isEqual:#"Broccoli"]) {
[spanishVeggieList addObject:SPANISH_Broccoli];
}
if ([current isEqual:#"Carrots"]) {
[spanishVeggieList addObject:SPANISH_Carrots];
}
if ([current isEqual:#"Squash"]) {
[spanishVeggieList addObject:SPANISH_Squash];
}
if ([current isEqual:#"Onions"]) {
[spanishVeggieList addObject:SPANISH_Onions];
}
if ([current isEqual:#"Tomatoes"]) {
[spanishVeggieList addObject:SPANISH_Tomatoes];
}
if ([current isEqual:#"And More"]) {
[spanishVeggieList addObject:SPANISH_More];
}
}
newAnnotation.fruits = spanishFruitList;
newAnnotation.veggies = spanishVeggieList;
}
newAnnotation.coordinate = location;
newAnnotation.vendorID = vendor_ID;
newAnnotation.startHour = startHr;
newAnnotation.endHour = endHr;
newAnnotation.loc = point;
newAnnotation.isCustomAddress = false;
//newAnnotation.subtitle = address;
__block NSString *address = [NSString stringWithFormat:#"%# %#, %#, %#, %#",
addressArray[0], addressArray[1],
addressArray[2], addressArray[3],
addressArray[4]];
__block NSString *currAddress = [NSString stringWithFormat:#"%# %#\n"
"%#, %#, %#\n"
"%#\n",
addressArray[0], addressArray[1],
addressArray[2], addressArray[3],
addressArray[4], addressArray[5]];
newAnnotation.subtitle = address;
newAnnotation.addressFormatted = currAddress;
static NSString *identifier = #"MyLocation";
MKPinAnnotationView *currentView = [[MKPinAnnotationView alloc] initWithAnnotation:newAnnotation reuseIdentifier:identifier];
[pointArray addObject:currentView];
} else {
//[self viewDidLoad];
NSLog(#"%#", error.debugDescription);
}
//} ];
}
} while (pointArray.count < query.countObjects);
}
if (pointArray.count == query.countObjects) {
for (MKPinAnnotationView *currentPoint in pointArray) {
[self.MyMapView addAnnotation:currentPoint.annotation];
}
}
}];
Thanks in advance for the help. I do not really understand why this code would not complete after only one iteration.
The NSLog(#"%#", error.debugDescription); doesn't look like it's in the right place. It's in an else block that is associated with the if (today <= newDate) which is inside a block of code that is only executed if error is null which is why it says null in the log (when what it really means is "today > newDate"). – Anna
I am working on IOS application.Integrated Dropbox successfully and saving data as record in datastores in DropBox as well.It was fine till here.But I am unable to get data after deleting application and reinstalling it.But in one scenario I am getting data i.e,"I inserted a record in any one of the tables in datastores,after inserting that when I am trying to get data Its coming successfully".But I need to get for the first time as the app installs.If any one worked on it please help me.Thanks in advance.
-(void)dropBoxScuccessLogin:(NSString *)successString
{
if ([successString isEqualToString:#"scuccess"])
{
//appdelegate.window.rootViewController = appdelegate.splitview;
NSArray *array1=[appdelegate.datastoreManager listDatastores:nil];
NSLog(#"array is %#",array1);
if (self.account)
{
NSDate *mydate=[NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMM-DD-yyyy hh:mm:ss a"];
NSString *stringFromDate = [formatter stringFromDate:mydate];
DBTable *customerTbl = [self.store getTable:#"DataSyncedOndate"];
DBRecord *task = [customerTbl insert:#{ #"SyncedDate": stringFromDate} ];
__weak DropBoxViewController *slf = self;
[self.store addObserver:self block:^ {
if (slf.store.status & (DBDatastoreIncoming | DBDatastoreOutgoing))
{
[self syncTasks];
}
}];
[self.store sync:nil];
}
}
else
{
NSLog(#"Dropbox Login faild");
}
}
- (void)syncTasks
{
NSLog(#"Self Account is in syncTasks is %#",self.account);
if (self.account)
{
NSDictionary *changed = [self.store sync:nil];
NSLog(#" Data is Synced");
// [self getDataSync];
dispatch_async(dispatch_get_main_queue(), ^{
[self retriveDataFromDB];
});
// [self performSelector:#selector(getDataSync) withObject:nil afterDelay:2.0];
}
else
{
// [alertView show];
}
}
in retriveDataFromDB method
-(void)retriveDataFromDB
{
NSLog(#"retrive from DB method called");
///////////Admin details///////////
NSMutableArray *tasks = [NSMutableArray arrayWithArray:[[self.store getTable:#"PriceList"] query:nil error:nil]];
NSLog(#"tasks count is %d",[tasks count]);
for (int k=0; k<[tasks count]; k++)
{
DBRecord *recordObj=[tasks objectAtIndex:k];
NSString *Tier1_Id =recordObj[#"Tier1"];
NSString *Tier2_Id =recordObj[#"Tier2"];
NSString *Tier3_Id =recordObj[#"Tier3"];
NSString *Code_Id =recordObj[#"Code"];
NSString *CRV_Id =recordObj[#"CRV"];
NSString *insertAdminString = [NSString stringWithFormat:#"INSERT INTO admin_Tbl(Code,Tier1,Tier2,Tier3,CRV) VALUES(\"%#\",\"%#\",\"%#\",\"%#\",\"%#\")",Code_Id,Tier1_Id,Tier2_Id,Tier3_Id,CRV_Id];
BOOL isDataadded = [appdelegate executeInsertQuery:insertAdminString];
if (isDataadded == YES)
{
NSLog(#"admin table insertrd successfully");
}
else
{
NSLog(#"admin table not insertrd successfully");
}
}
}
In Log I am getting tasks count is "0".
I'm having some trouble with lag in my UITableview.
There aren't any problems on an iPhone 5 and after I started caching images in an NSDictionary, the iPhone 4 with iOS 6 became very responsive.
The problem remains on an iPhone 4 with iOS 7 however.
I've read trough some threads here with tips about making views opaque which I did but it didn't help. All my views except the labels are opaque (because if they are opaque they fill and that won't work for my purpose)
I do load a background image from the storyboard, do you guys know if this might be affecting performance? Is the storyboard inefficient when it comes to loading images?
Do you have any other tips for improving performance on a UITableView?
Thanks in advance!
Some code as requested,and these are the elements on the cell: http://imgur.com/Dcif6QE
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
HomeListCell *cell;
if([self.eventList lastObject])
{
static NSString *CellIdentifier = #"HomeListCell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.userInteractionEnabled = YES;
cell.event = [self.eventList objectAtIndex:indexPath.row];
cell.parent = self;
if([cell.event.event.event_type count] != 0)
{
Event_Type *eventType = [cell.event.event.event_type firstObject];
NSString *imageName = #"HomeList_Type";
imageName = [imageName stringByAppendingString:eventType.name];
cell.eventTypeImage.image = [self.imageDict objectForKey:imageName];
}
//Laad de images hier uit de cache om scroll performance te verbeteren
int score = [cell.event.rating intValue];
[cell moveView:cell.ratingNumber duration:0.0 curve:UIViewAnimationCurveLinear x:0.0 y:0.0];
if(score > 0)
{
cell.ratingImage.image = [self.imageDict objectForKey:#"HomeList_plus"];
}
else if(score == 0)
{
cell.ratingImage.image = nil;
[cell moveView:cell.ratingNumber duration:0.0 curve:UIViewAnimationCurveLinear x:0.0 y:-10.0];
}
else
{
cell.ratingImage.image = [self.imageDict objectForKey:#"HomeList_min.png"];
score = -score;
}
cell.ratingNumber.text = [NSString stringWithFormat:#"%d", score];
[cell styleSelf];
}
And styleSelf has this code:
-(void) styleSelf {
LocationManager *locationManager = [LocationManager sharedInstance];
//Tekens die verandert moeten worden
NSCharacterSet *notAllowedY = [NSCharacterSet characterSetWithCharactersInString:#"ÿ"];
NSString *resultString = [[event.event.name componentsSeparatedByCharactersInSet:notAllowedY] componentsJoinedByString:#"y"];
//Afstand berekening
double eventLong = [self.event.location.address.gps_long doubleValue];
double eventLat = [self.event.location.address.gps_lat doubleValue];
CLLocation* locatie = [[CLLocation alloc]initWithLatitude:eventLat longitude:eventLong];
//Date + time
NSString *eventDate = event.opening;
eventDate = [eventDate stringByReplacingOccurrencesOfString:#"T" withString:#" "];
NSDate *theDate;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc]init];
if([eventDate hasSuffix:#"Z"])
{
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ssZ"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"UTC"]];
theDate = [dateFormatter dateFromString:eventDate];
}
else
{
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
theDate = [dateFormatter dateFromString:eventDate];
}
[dateFormatter setDateFormat:#"HH:mm"];
self.timeNumberLabel.text = [dateFormatter stringFromDate:theDate];
self.timeNumberAfscheurLabel.text = [dateFormatter stringFromDate:theDate];
[dateFormatter setDateFormat:#"MM-dd"];
if ([[dateFormatter stringFromDate:theDate] isEqualToString:[dateFormatter stringFromDate:[NSDate date]]])
{
self.timeWhenLabel.text = NSLocalizedString(#"HomeList-Vandaag", nil);
self.timeWhenAfscheurLabel.text = NSLocalizedString(#"HomeList-Vandaag", nil);
}
else
{
[dateFormatter setDateFormat:#"MM"];
NSString *maand = [dateFormatter stringFromDate:theDate];
NSString *monthName = NSLocalizedString([#"Maand-" stringByAppendingString: maand], nil);
[dateFormatter setDateFormat:#"d"];
NSString *dag = [dateFormatter stringFromDate:theDate];
NSString *DatumString = [[dag stringByAppendingString:#" "]stringByAppendingString:monthName];
self.timeWhenLabel.text = [#" " stringByAppendingString:DatumString];
self.timeWhenAfscheurLabel.text = [#" " stringByAppendingString:DatumString];
}
//De cell vormen of de user gaat of niet
if([event.user_attends_event count] == 0)
{
[self moveView:self.nietAfgescheurdKnop duration:0 curve:UIViewAnimationCurveLinear x:0.0 y:0.0];
[self moveView:self.timeNumberAfscheurLabel duration:0 curve:UIViewAnimationCurveLinear x:0.0 y:0.0];
[self moveView:self.timeWhenAfscheurLabel duration:0 curve:UIViewAnimationCurveLinear x:0.0 y:0.0];
}
else
{
[self moveView:self.nietAfgescheurdKnop duration:0 curve:UIViewAnimationCurveLinear x:50.0 y:0.0];
[self moveView:self.timeNumberAfscheurLabel duration:0 curve:UIViewAnimationCurveLinear x:50.0 y:0.0];
[self moveView:self.timeWhenAfscheurLabel duration:0 curve:UIViewAnimationCurveLinear x:50.0 y:0.0];
}
self.event.userDistance = [locationManager getDistanceBetween:locatie];
if([self.event.userDistance isEqualToString:#"GPS error"])
{
self.distanceNumberLabel.text = NSLocalizedString(#"Extras-GPS", nil);
self.distanceTypeLabel.text = NSLocalizedString(#"Extras-UIT", nil);
self.distanceNumberLabel.textColor = [UIColor grayColor];
self.distanceTypeLabel.textColor = [UIColor grayColor];
}
else
{
NSString *placehold = self.event.userDistance;
placehold = [placehold stringByReplacingOccurrencesOfString:#"." withString:#","];
self.distanceNumberLabel.text = placehold;
self.distanceTypeLabel.text = NSLocalizedString(#"Extras-Km", nil);
self.distanceNumberLabel.textColor = [UIColor blackColor];
self.distanceTypeLabel.textColor = [UIColor blackColor];
}
// Configure the cell...
self.titleLabel.text = resultString;
self.tagsLabel.text = [event getMetadataString];
}
Your evil culprit is NSDateFormatter. This is a super-heavy object to create. You should create a single version of it somewhere and reuse it, setting the properties (formats, time zones, etc.) freely.
It's also a good idea to use Instruments -> Time Profiler to see exactly which methods are taking up time on the main thread.