Search code examples
iosperformanceuitableviewuicollectionviewnsmutableattributedstring

Is NSMutableAttributedString with NSTextAttachment creation in cellForItemAtIndexPath a bad idea (performance or design wise)?


I am creating NSMutableAttributedString in the cellForItemAtIndexPath method of my collection view. I am using NSTextAttachment to embed images in the text.

Is this a bad idea? Currently the scroll performance seems good but I am not sure if there is a better way? Would caching all the NSMutableAttributedString in a NSMutableDictionary be better for the second scroll over?

Same question can be applied to a UITableview too using cellForRowAtIndexPath.

Code:

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    CellView *cell = [collectionView dequeueReusableCellWithReuseIdentifier:[CellView reuseIdentifier] forIndexPath:indexPath];

    NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
    attachment.image = [UIImage imageNamed:@"eye"];
    attachment.bounds = CGRectMake(0, -2.5, 14,14);
    NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:attachment];

    NSMutableAttributedString *myString= [[NSMutableAttributedString alloc] initWithString:@""];
    [myString appendAttributedString:attachmentString];

    [myString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" 19K    "]];

    NSTextAttachment *attachment2 = [[NSTextAttachment alloc] init];
    attachment2.image = [UIImage imageNamed:@"heart"];
    attachment2.bounds = CGRectMake(0, -2.5, 14,14);
    NSAttributedString *attachmentString2 = [NSAttributedString attributedStringWithAttachment:attachment2];

    [myString appendAttributedString:attachmentString2];

    [myString appendAttributedString:[[NSMutableAttributedString alloc] initWithString:@" 13K    "]];

    [myString enumerateAttribute:NSFontAttributeName inRange:(NSRange){0,[myString length]} options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
        [myString addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"AvenirNext-Medium" size:12] range:range];
        [myString addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithWhite:1 alpha:1] range:range];
    }];



    NSMutableAttributedString *titletext= [[NSMutableAttributedString alloc] initWithString:[self checkIfBig:indexPath]?[NSString stringWithFormat:@"\nThe Underground Railway"]:[NSString stringWithFormat:@"\nPink Oasis"]];

    [titletext enumerateAttribute:NSFontAttributeName inRange:(NSRange){0,[titletext length]} options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
        [titletext addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"AvenirNext-Medium" size:24] range:range];
        [titletext addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:range];
    }];

    [myString appendAttributedString:titletext];

    if ([self checkIfBig:indexPath]){
        NSMutableAttributedString *subtitletext= [[NSMutableAttributedString alloc] initWithString:@"\nLorem Ipsum is simply dummy text of the printing and typesetting industry."];

        [subtitletext enumerateAttribute:NSFontAttributeName inRange:(NSRange){0,[subtitletext length]} options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
            [subtitletext addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"AvenirNext-Medium" size:14] range:range];
            [subtitletext addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:range];
        }];
        [myString appendAttributedString:subtitletext];
    };

    if ([self checkIfBig:indexPath]){
        NSMutableAttributedString *subtitletext= [[NSMutableAttributedString alloc] initWithString:@"\n\n#HORROR #BLOOD"];

        [subtitletext enumerateAttribute:NSFontAttributeName inRange:(NSRange){0,[subtitletext length]} options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
            [subtitletext addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"AvenirNext-MediumItalic" size:14] range:range];
            [subtitletext addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:range];
        }];
        [myString appendAttributedString:subtitletext];
    };

    cell.titleLabel.attributedText=myString;


    return cell;
}

Solution

  • As a general rule, your data should be assembled once before the table view or collection view is loaded. In your current code, as a user scrolls back and forth you are recreating the same data over and over again. That's pretty inefficient.

    All of your data should be in a single array (or array of arrays if you have multiple sections). Your cellForRow|ItemAtIndexPath should simply get an object from the array and that object's properties should be used to populate the cell.

    If you have many rows or some of the data is more expensive, you can improve the above by creating certain data elements on demand and caching them so you still only create each one once.

    When done properly, the cellForItemAtIndexPath in your question should be less than 5 lines of code.