Autoresizing KILabel in UITableViewCell - ios

I have a custom UILabel that I download from Github (called KILabel). The reason I got it because I wanted to duplicate Instagram's activity feed(The segmented control and the UITableViews with 2 different sets of information). In instagram's app I know there is the ability to tap on people usernames within the label that is contained in each and ever UITableViewCell(This is where KILabel comes into play). KILabel recognizes "#usernames", "#hashtags", and URLs and makes them tappable almost like a UIButton.
I had previously went without the KILabel and used the regular UILabel and it was able to expand in iOS 8 and iOS 7. Now, when I use the KILabel it auto expands(Dynamically changes its height to fit the text) ONLY on iOS 7 and it does NOT start off expanded. When I scroll in my UITableViewController, that is when the label expands. It starts off normal height.
As of right now this what the Custom Cell looks like
#import <UIKit/UIKit.h>
#import "KILabel.h"
#interface NewsCell : UITableViewCell
#property (strong, nonatomic) IBOutlet KILabel *cellLabel;
#property (strong, nonatomic) IBOutlet UIImageView *cellImageView;
#end
I have the label in the interface(storyboard) hooked up to the custom KILabel class as well as the ImageView.
Next I have the cell being implemented in the UITableViewController. I tested it with a large piece of text to see if the cell and label would expand to the correct size
cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier2 forIndexPath:indexPath];
cell.cellImageView.layer.cornerRadius = 6.0f;
cell.cellImageView.clipsToBounds = YES;
cell.cellImageView.layer.borderWidth = 1.0f;
cell.cellImageView.layer.borderColor = [[UIColor blackColor] CGColor];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] init];
longPress.numberOfTapsRequired = 1;
[cell.cellLabel addGestureRecognizer:longPress];
//Create the cell label going into the cell
cell.cellLabel.linkTapHandler = ^(KILinkType linkType, NSString *string, NSRange range) {
NSString *mString = [string stringByReplacingOccurrencesOfString:#"#" withString:#""];
if (linkType == KILinkTypeURL) {
// Open URLs
//[self attemptOpenURL:[NSURL URLWithString:string]];
} else if (linkType == KILinkTypeUserHandle) {
if (longPress.state == UIGestureRecognizerStatePossible) {
[self tapOnUsername:longPress username:mString];
}
} else {
// Put up an alert with a message if it's not an URL
NSString *linkTypeString = #"Username";
if (linkType == KILinkTypeHashtag)
{
linkTypeString = #"Hashtag";
}
NSString *message = [NSString stringWithFormat:#"You tapped %# which is a %#", string, linkTypeString];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hello"
message:message
delegate:nil
cancelButtonTitle:#"Dismiss"
otherButtonTitles:nil];
[alert show];
}
};
PFObject *eachNews = [self.followingNews objectAtIndex:indexPath.row];
PFUser *notifier = [eachNews objectForKey:#"Notifier"];
PFUser *notified = [eachNews objectForKey:#"Notified"];
[notifier fetchIfNeeded];
[notified fetchIfNeeded];
NSString *notifierString = [[NSString alloc] init];
NSString *notifiedString = [[NSString alloc] init];
NSString *grammer = [[NSString alloc] init];
NSDate *timeStamp = eachNews.createdAt;
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMM d, yyyy h:mm a"];
NSString *timeString = [timeStamp formattedAsTimeAgo];
if ([notifier.username isEqualToString:_loggedInUser.username]) {
notifierString = #"You";
grammer = #"are";
} else {
notifierString = [NSString stringWithFormat:#"#%#", notifier.username];
grammer = #"is";
}
if ([notified.username isEqualToString: _loggedInUser.username]) {
notifiedString = #"you";
} else {
notifiedString = [NSString stringWithFormat:#"#%#", notified.username];
}
if (notifier[#"profileImage"] == nil) {
UIImage *hermet = [UIImage imageNamed:#"user"];
[cell.cellImageView setImage:hermet];
} else {
PFFile *imageFile = notifier[#"profileImage"];
[cell.cellImageView setImage:[UIImage imageWithData:[imageFile getData]]];
}
NSMutableString *newsText = [[NSMutableString alloc] init];
if ([eachNews[#"Type"] isEqualToString:#"Follow"]) {
[newsText appendString:[NSString stringWithFormat:#"%# aaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUVaaaaaaaaaaaaaaasdasafsfsdgsdgsdfgsdfsodjnfsaioefgnarpuoigbweuifbsdpugbdsfiougbsdiugosbdgiusobfasdioFPBSADUBVSIUDVBSAIUDBSDIUVBSDUVIBDSFIUVBSDIUVSBAVIPUBDSIUV%# %#.\n", notifierString, eachNews[#"Messages"], notifiedString]];
} else if ([eachNews[#"Type"] isEqualToString:#"New Founder"]) {
[newsText appendString:[NSString stringWithFormat:#"%# %# %#.\n", notifierString, grammer, eachNews[#"Messages"]]];
} else if ([eachNews[#"Type"] isEqualToString:#"Club Create"]) {
if([notified.username isEqualToString:_loggedInUser.username]) {
notifiedString = #"You";
} else {
notifiedString = notified.username;
}
if (notified[#"profileImage"] == nil) {
UIImage *hermet = [UIImage imageNamed:#"user"];
[cell.cellImageView setImage:hermet];
} else {
PFFile *imageFile = notified[#"profileImage"];
[cell.cellImageView setImage:[UIImage imageWithData:[imageFile getData]]];
}
[newsText appendString:[NSString stringWithFormat:#"%# %#\n", notifiedString, eachNews[#"Messages"]]];
}
[newsText appendString:timeString];
NSArray *appendedString = [newsText componentsSeparatedByString:#"\n"];
NSRange dateRange = [newsText rangeOfString:appendedString[1]];
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:newsText];
[attrString beginEditing];
[attrString addAttribute: NSForegroundColorAttributeName
value:[UIColor lightGrayColor]
range:dateRange];
[attrString endEditing];
cell.cellLabel.attributedText = attrString;
CGSize sizeForLabel = CGSizeMake(cell.cellLabel.frame.size.width, 0);
CGRect labelRect = CGRectMake(cell.cellLabel.frame.origin.x, cell.cellLabel.frame.origin.y, sizeForLabel.width, CGFLOAT_MAX);
// Use a dummy label and its textRectForBounds method to calculate the height
// of a real label.
KILabel *measureLabel = [[KILabel alloc] initWithFrame:CGRectMake(cell.cellLabel.frame.origin.x, cell.cellLabel.frame.origin.y, labelRect.size.width, 0)];
measureLabel.numberOfLines = 0;
measureLabel.attributedText = attrString;
labelRect = [measureLabel textRectForBounds:labelRect limitedToNumberOfLines:0];
cell.cellLabel.frame = labelRect;
The height of UITableViewCell is completely fine and expands perfectly. The label is the only issues. It does not expand at all on iOS 8 and expands ONLY AFTER I have scrolled in the UITableViewController on iOS 7.
I would tagged KILabel but I can't and if anyone has used another UILabels like KILabel and been able to get them auto-expand please refer me to them.
KILabel on GitHub: https://github.com/Krelborn/KILabel
Also, I have already tried the Gist the creator posted on his GitHub and that will get my cell to expand perfectly.

Related

custom label not being filled with data completely

I have the following code:
if (product) {
if ([product.title isStringOfInterest]) {
scriptName = [[PPLinearLayoutLabelItem alloc] initWithText:#"" font:[PPFonts bold20] maxWidth:[LayoutValues getMaxWidthClipped]];
[self.topContainerContent addObject:scriptName];
extraName = [[PPLinearLayoutLabelItem alloc] initWithText:#"" font:[PPFonts regular14] maxWidth:[LayoutValues getMaxWidthClipped]];
[scriptName setPaddingTop:20];
[self.topContainerContent addObject:extraName];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void){
[self loadProductDetails];
});
-(void) loadProductDetails{
IHProduct *product = orderDisplayed.product;
id<ProductDownloadServiceProtocol> productDetailsDownloader = [[ProductDownloadService alloc] initWithClient:[[HttpClient alloc] initWith:APIbackend forEndpoint:EndpointProduct]];
[productDetailsDownloader downloadProductWithProductID:product.productID success:^(ProductDownloadResponse *response) {
scriptName.label.text = [NSString stringWithFormat:#"%# (%#)",response.product.title,response.product.pillTypeShort];
extraName.label.text = response.product.sameAs;
NSString *qtyText = [NSString stringWithFormat:#"%# PACKS (%# at $%# per pack)",[orderDisplayed.interval objectForKey:#"quantity_per_interval"], [orderDisplayed.interval objectForKey:#"quantity_per_interval"], response.product.price];
quantity.label.text = qtyText;
} error:^(ProductDownloadResponse *response) {
[self hideHttpLoadingOverlay];
[RMUniversalAlert showAlertInViewController:self
withTitle:alertErrorTitle
message:#"Error downloading mail order product details"
cancelButtonTitle:OKDialogButton
destructiveButtonTitle:nil
otherButtonTitles:nil
tapBlock:nil];
}];
}
The labels extraname and scriptname are getting filled or getting chopped off in the end i.e. incomplete data. How can I fix this? When I statically put large texts in this the data gets filled into those views nicely. Help!
If you wish adjust width of UILabel based on it text content then use below code;
NSDictionary *fontAttributes = #{NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue" size:14]};
CGRect rectOfText = [textToMeasure boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:fontAttributes
context:nil];
CGRect tempFrame = self.label.frame;
tempFrame.size.width = rectOfText.size.width;
self.label.frame = tempFrame;
And if you wish to change its font based on uilabel width then use below;
NOTE : This will work only if self.label.numberOfLines = 1.
self.label.adjustsFontSizeToFitWidth = YES;
self.label.minimumFontSize = 0;

iOS 8.1 Only Displaying UITextView Intermittently

I have a template quiz application that has been on the app store in various guises for a while now, in general it receives good reviews and had no bug reports.
Recently I've had two bug reports, people using iPads with iOS 8.1.2 or 8.1.3, saying that now and again the UITextView that I use to show the questions is blank.
I've not been able to replicate this bug, but I would be grateful if someone could shed some light on it.
The objects questions and userAnswer are not nil so it is definitely a UITextView issue.
The Controller is here:
#interface ReviewController ()
#property (strong, nonatomic) IBOutlet UITextView *output;
#end
#implementation ReviewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setReviewText];
// Do any additional setup after loading the view.
}
-(void)setReviewText{
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSMutableArray* userAnswer = [[NSMutableArray alloc] init];
userAnswer = [self answersAttributed];
if(questions == nil)
_output.text = #"Questions Error";
if (userAnswer == nil)
{
_output.text = #"Error";
}
NSMutableAttributedString *mutableAttString = [[NSMutableAttributedString alloc] init];
int i = 0;
NSAttributedString *linebreak =[[NSAttributedString alloc] initWithString:#"\n"];
for (Question* q in questions)
{
if (i == [userAnswer count])
break;
NSAttributedString *qtext =[[NSAttributedString alloc] initWithString:q.questionText];
NSAttributedString *ctext =[[NSAttributedString alloc] initWithString:#"Correct Answer:\n"];
NSAttributedString *atext =[[NSAttributedString alloc] initWithString:q.answerText];
NSAttributedString *ytext =[[NSAttributedString alloc] initWithString:#"Your Answer:\n"];
NSAttributedString *utext =[[NSAttributedString alloc] initWithAttributedString:userAnswer[i]];
[mutableAttString appendAttributedString:qtext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ctext];
[mutableAttString appendAttributedString:atext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:ytext];
[mutableAttString appendAttributedString:utext];
[mutableAttString appendAttributedString:linebreak];
[mutableAttString appendAttributedString:linebreak];
i++;
}
NSAttributedString* outText = [[NSAttributedString alloc] init];
outText = [mutableAttString mutableCopy];
_output.attributedText = outText;
[_output setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
}
-(NSMutableArray*)answersAttributed
{
NSMutableArray* ansAtt = [[NSMutableArray alloc] init];
NSArray* questions = [[NSArray alloc] init];
questions = [_manager getAllQuestions];
NSArray* userAnswers = [[NSArray alloc] init];
userAnswers = [_manager getAllUserAnswers];
if ([questions count] != [userAnswers count])
{
return ansAtt;
}
int i = 0;
for (NSString* userAnswer in userAnswers )
{
if([[questions[i] answerText] isEqualToString:userAnswer] )
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor greenColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
else
{
NSDictionary *attributes = #{NSBackgroundColorAttributeName:[UIColor redColor]};
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:userAnswer attributes:attributes];
[ansAtt addObject:attrString];
}
i++;
}
return ansAtt;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Make sure your segue name in storyboard is the same as this line
if ([[segue identifier] isEqualToString:#"review2results"])
{
// Get reference to the destination view controller
ReviewController *vc = [segue destinationViewController];
// Pass any objects to the view controller here, like...
[vc setManager:_manager];
}
}
With autolayout, if you don't have enough constraints to fully specify size and position, then sometimes you'll see and and sometimes you won't. The unspecified constraints can have random values, like 0 height or off screen position.
Check to make sure the constraints are fully specified.
In my case textview marked as Non-Editable and Non-Selectable in Storyboard have the same strange behavior on iOS 8.1 (no problems in iOS 9+).
- (void)viewDidLoad {
[super viewDidLoad];
textview.attributedText = [[NSAttributedString alloc] initWithString:#"non-nil"]];
value = textview.attributedText;
//^ value is nil !!!
}
Fixed with this:
- (void)viewDidLoad {
[super viewDidLoad];
textview.editable = YES;
textview.attributedText = [[NSAttributedString alloc] initWithString:#"non-nil"]];
textview.editable = NO;
value = textview.attributedText;
//^ value is #"non-nil" now !!!
}

Conditionally Add Cell Data to NSMutable Array

Having a lot of trouble with this today. Finally decided to turn to the experts.
I have a standard Table View Controller. Each cell has a built-in UIImage with a tap gesture recognizer that toggles the image back and forth from a green checkmark to a red x. That's working just fine.
What I'm trying to do now is write a conditional statement that says: When the image is a green checkmark, add the cell textLabel to the saved NSMutableArray; else remove red x objects from the array (that may have been added at an earlier time). Currently I'm getting "null" in the console on both accounts.
Here's the original method:
-(void)imageTapped:(UIGestureRecognizer*)gesture
{
UIImageView *selectedImageView=(UIImageView*)[gesture view];
UIImage *imageRed = [UIImage imageNamed:#"checkmark(red).png"];
UIImage *imageGreen = [UIImage imageNamed:#"checkmark(green).png"];
NSString *address = #" ";
NSString *name = #" ";
UITableViewCell *cell = [self.placesTableView cellForRowAtIndexPath:self.placesTableView.indexPathForSelectedRow];
name = cell.textLabel.text;
address = cell.detailTextLabel.text;
NSMutableArray *saved = [[NSMutableArray alloc] initWithObjects:#"%#",#"%#", name, address, nil];
NSString *csv = [NSString stringWithFormat:#"%#, %#", name, address];
if (selectedImageView.image == imageGreen) {
selectedImageView.image = imageRed;
[saved removeObject:csv];
NSLog(#"Deselected" #"%#", name);
} else {
selectedImageView.image = imageGreen;
[saved addObject:csv];
NSLog(#"Selected" #"%#", name);
}
}
EDITED: Suggested method (still not working):
h: #property (strong, nonatomic) NSMutableArray *saved;
m:
- (void)viewDidLoad
{
_saved = [[NSMutableArray alloc] init];
}
-(void)imageTapped:(UIGestureRecognizer*)gesture
{
UIImageView *selectedImageView=(UIImageView*)[gesture view];
UIImage *imageRed = [UIImage imageNamed:#"checkmark(red).png"];
UIImage *imageGreen = [UIImage imageNamed:#"checkmark(green).png"];
NSString *address = #" ";
NSString *name = #" ";
UITableViewCell *cell = [self.placesTableView cellForRowAtIndexPath:self.placesTableView.indexPathForSelectedRow];
name = cell.textLabel.text;
address = cell.detailTextLabel.text;
NSString *csv = [NSString stringWithFormat:#"%#, %#", name, address];
self.saved = [NSMutableArray array];
//NSData *imageRedData = UIImagePNGRepresentation(imageRed);
NSData *imageGreenData = UIImagePNGRepresentation(imageGreen);
NSData *selectedImageViewData = UIImagePNGRepresentation(selectedImageView.image);
if ( [selectedImageViewData isEqual: imageGreenData]) {
selectedImageView.image = imageRed;
[self.saved removeObject:csv];
NSLog(#"Deselected" #"%#", name);
} else {
selectedImageView.image = imageGreen;
[self.saved addObject:csv];
NSLog(#"Selected" #"%#", name);
}
}
Do you mean to create the saved mutable array every time? It won't persist past the end of the imageTapped method. Perhaps you want this NSMutableArray to be a property (or ivar) and add-to or remove from self.saved?
you need to change the UIImage comparison logic, instead of using = operator use isEqual method on NSObject. Here is a solution code which help you.
-(void)imageTapped:(UIGestureRecognizer*)gesture
{
UIImageView *selectedImageView=(UIImageView*)[gesture view];
UIImage *imageRed = [UIImage imageNamed:#"checkmark(red).png"];
UIImage *imageGreen = [UIImage imageNamed:#"checkmark(green).png"];
NSString *address = #" ";
NSString *name = #" ";
UITableViewCell *cell = [self.placesTableView cellForRowAtIndexPath:self.placesTableView.indexPathForSelectedRow];
name = cell.textLabel.text;
address = cell.detailTextLabel.text;
NSMutableArray *saved = [[NSMutableArray alloc] initWithObjects:#"%#",#"%#", name, address, nil];
NSString *csv = [NSString stringWithFormat:#"%#, %#", name, address];
NSData *imageRedData = UIImagePNGRepresentation(imageRed);
NSData *imageGreenData = UIImagePNGRepresentation(imageGreen);
NSData *selectedImageViewData = UIImagePNGRepresentation(selectedImageView.image);
if ( [selectedImageViewData isEqual: imageGreenData]) {
selectedImageView.image = imageRed;
[saved removeObject:csv];
NSLog(#"Deselected" #"%#", name);
} else {
selectedImageView.image = imageGreen;
[saved addObject:csv];
NSLog(#"Selected" #"%#", name);
}
}

iPhone 4 iOS 7 Tableview lag when scrolling

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.

UITableView with complex cells is slow and laggy

I've almost finished my app and everything seems to work but the main view.
It's an UIViewController with an embedded UITableView.
I'm using Parse as the backend, and I get an array of the objects I need in my viewDidLoad method.
Each cell contains some data that I'm fetching in the tableView:cellForRowAtIndexPath and I'm afraid that this is the reason why my table view is so laggy, but I don't know how to fetch the data I need for each object in my array without having the indexPath.row number.
I've already made each cell element "opaque" as suggested in other answers.
This is my code, any help would be greatly appreciated:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"cellHT";
CellHT *cell = (CellHT *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[CellHT alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// self.hH is an NSArray containing all the objects
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
cell.lblTitle.text = [self.hH[indexPath.row] objectForKey:#"title"];
cell.lblVenueName.text = [self.hH[indexPath.row] objectForKey:#"venueName"];
cell.lblDistance.text = NSLocalizedString(#"Distance from you", nil);
self.geo = [self.hH[indexPath.row] objectForKey:#"coordinates"];
// the formatters are initialized in the viewDidLoad: method
self.formatData = [NSDateFormatter dateFormatFromTemplate:#"dd/MM" options:0 locale:[NSLocale currentLocale]];
[self.formatterData setDateFormat:self.formatData];
self.formatOra = [NSDateFormatter dateFormatFromTemplate:#"j:mm" options:0 locale:[NSLocale currentLocale]];
[self.formatterOra setDateFormat:self.formatOra];
self.dal = NSLocalizedString(#"from", nil);
self.ore = NSLocalizedString(#"at", nil);
CLLocation *vLoc = [[CLLocation alloc] initWithLatitude:self.geo.latitude longitude:self.geo.longitude];
CLLocation *user = [[CLLocation alloc] initWithLatitude:self.userGeo.latitude longitude:self.userGeo.longitude];
CLLocationDistance distance = [user distanceFromLocation:venueLoc];
if ([[prefs objectForKey:#"unit"] isEqualToString:#"km"]) {
cell.lblDist.text = [NSString stringWithFormat:#"%.1f Km", distance /1000];
} else {
cell.lblDist.text = [NSString stringWithFormat:#"%.1f Miles", distance /1609];
}
// compare the object's starting date with the current date to set some images in the cell
NSComparisonResult startCompare = [[self.hH[indexPath.row] objectForKey:#"startDate"] compare: [NSDate date]];
if (startCompare == NSOrderedDescending) {
cell.quad.image = [UIImage imageNamed:#"no_HT"];
cell.lblStartTime.textColor = [UIColor redColor];
} else {
cell.quad.image = [UIImage imageNamed:#"yes_HT"];
cell.lblStartTime.textColor = [UIColor colorWithRed:104.0/255.0 green:166.0/255.0 blue:66.0/255.0 alpha:1.0];
}
NSString *dataInizio = [NSString stringWithFormat:#"%# %# %# %#", self.dal, [self.formatterData stringFromDate:[self.hH[indexPath.row] objectForKey:#"startDate"]], self.ore, [self.formatterOra stringFromDate:[self.hH[indexPath.row] objectForKey:#"endDate"]]];
cell.lblStartTime.text = dataInizio;
PFObject *cat = [self.hH[indexPath.row] objectForKey:#"catParent"];
NSString *languageCode = [[NSLocale preferredLanguages] objectAtIndex:0];
if ([languageCode isEqualToString:#"it"]) {
cell.lblCategory.text = [cat objectForKey:#"nome_it"];
} else if ([languageCode isEqualToString:#"es"]) {
cell.lblCategory.text = [cat objectForKey:#"nome_es"];
} else {
cell.lblCategory.text = [cat objectForKey:#"nome_en"];
}
//getting the image data from the Parse PFFile
PFFile *theImage = [self.hH[indexPath.row] objectForKey:#"photo"];
[theImage getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
cell.cellImageView.image = [UIImage imageWithData:data];
}
}];
//getting the cell object's owner and his profile
PFUser *usr = [self.hH[indexPath.row] objectForKey:#"parent"];
PFQuery *prof = [PFQuery queryWithClassName:#"Profile"];
prof.cachePolicy = kPFCachePolicyCacheThenNetwork;
[prof whereKey:#"parent" equalTo:usr];
[prof getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
//getting the object's rating and the number of votes
PFQuery *rateQuery = [PFQuery queryWithClassName:#"Rating"];
[rateQuery whereKey:#"parent" equalTo:object];
[rateQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
float vote = [[object objectForKey:#"rate"] floatValue];
float temp = ((vote * 2) + 0.5);
int tempvote = (int)temp;
float roundedVote = (float)tempvote / 2;
// drawing the stars number, depending on the rating obtained
UIImage *starsImage = [UIImage imageNamed:#"stars"];
UIGraphicsBeginImageContextWithOptions(cell.imgVoto.frame.size, NO, 0);
CGPoint starPoint = (CGPoint) {
.y = (cell.imgVoto.frame.size.height * (2 * roundedVote + 1)) - (starsImage.size.height)
};
[starsImage drawAtPoint:starPoint];
cell.imgVoto.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
cell.lblVoto.text = [NSString stringWithFormat:#"(%d)", [[object objectForKey:#"voters"] intValue]];
}
}];
}
}];
return cell;
}
EDIT: this is the cell code:
+ (void)initialize {
if (self != [HH class]) {
return;
}
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if ( !(self = [super initWithCoder:aDecoder]) ) return nil;
self.cellImageView.image = [UIImage imageNamed:#"icona_foto"];
self.cellImageView.contentMode = UIViewContentModeScaleToFill;
self.formatterData = [[NSDateFormatter alloc] init];
self.formatData = [[NSString alloc] init];
self.formatterOra = [[NSDateFormatter alloc] init];
self.formatOra = [[NSString alloc] init];
self.formatData = [NSDateFormatter dateFormatFromTemplate:#"dd/MM" options:0 locale:[NSLocale currentLocale]];
[self.formatterData setDateFormat:self.formatData];
self.formatOra = [NSDateFormatter dateFormatFromTemplate:#"j:mm" options:0 locale:[NSLocale currentLocale]];
[self.formatterOra setDateFormat:self.formatOra];
self.lblVoto.text = #"(0)";
return self;
}
SECOND EDIT: this is the code in the viewDidLoad method:
PFQuery *hours = [PFQuery queryWithClassName:#"HH"];
hours.cachePolicy = kPFCachePolicyCacheThenNetwork;
// here I'm making lots of query constraints that I'll not include
[hours findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.objectsNumber = objects.count;
self.hH = [[NSArray alloc] initWithArray:objects];
}
}];
[self.tableView reloadData];
}
I would move as much of the logic out of cellForRowAtIndexPath: as you can, it needs to be very light-weight to get good scrolling performance. You're doing a lot of work on the main thread, and I would do a lot more of this work when you get your model objects back from Parse (if you could post viewDidLoad I can give you more specific help) and update the table view when these calls are done:
[UIImage imageWithData:data]
anything to do with NSDateFormatter
CLLocation's initWithLatitude:longitude:
creating the rating stars image
None of these depend on the state of the table view, so they can be effectively precomputed and cached in a model object. If you simply scroll up and down the table, you're doing allo f the same work over and over, killing your performance.
Updated for the questioner's newest code:
I won't include all of your functionality here but this should give you an idea:
// create a single shared formatter instead of one per object
NSDateFormatter *dateFormatter = [NSDateFormatter dateFormatFromTemplate:#"dd/MM" options:0 locale:[NSLocale currentLocale]];
NSDateFormatter *timeFormatter = [NSDateFormatter dateFormatFromTemplate:#"j:mm" options:0 locale:[NSLocale currentLocale]];
[hours findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.objectsNumber = objects.count;
for (SomeObject *modelObj in objects) {
// if you can add properties to your model object directly, do that
// otherwise write a category on the Parse object to add the ones you need
modelObj.dateString = [NSString stringWithFormat:#"%# %# %# %#", modelObj.dal, [self.dateFormatter stringFromDate:[modelObj objectForKey:#"startDate"]], modelObj.ore, [self.timeFormatter stringFromDate:[modelObj objectForKey:#"endDate"]]];
// create your locations, images, etc in here too
}
self.hH = [[NSArray alloc] initWithArray:objects];
}
}];]
Then in cellForRowAtIndexPath:, take the precomputed properties and simply assign them to the appropriate labels, image views, etc.
It would be even better to do most of this processing off the main thread via GCD, but that is most likely out of scope for this question. See Using GCD and Blocks Effectively for more information. Just remember do only interact with UIKit from the main thread!
have a try by removing
CLLocation *vLoc = [[CLLocation alloc] initWithLatitude:self.geo.latitude longitude:self.geo.longitude];
CLLocation *user = [[CLLocation alloc] initWithLatitude:self.userGeo.latitude long itude:self.userGeo.longitude];
CLLocationDistance distance = [user distanceFromLocation:venueLoc];
This was at first sight , then I see your all your code and I realize a lot of image are used
Because UITableView takes some time to layout cells.
Solution:
step1. Set section number and row number to 0.
step2. Reload tableView in viewDidAppear.
Then your view controller cloud response quickly and then show cells.

Resources