Attach Stars to the End of a UILabel

In the past few days, I came across with a problem that when dealing with UILabel, which it’s content itself is a NSString. I need to

  • Attach some images (In my case, some stars) right by the end of the text in the UILabel
  • When there isn’t enough space in the end of the text for displaying the images, it auto wraps to the next line. Just like how UILabel deals with words wapping.
  • Attach it at whichever place in the text as I want

Here is how the effect looks like when it’s done.

  • When there is enough space in the end, enter image description here
  • When there is no enough space in the end, enter image description here

So my way here to tackle this issue, is to use NSTextAttachment. NSTextAttachment takes a UIImage as argument , the NSTextAttachment itself then can be attached to an empty NSAttributedString by calling:

1
[NSAttributedString attributedStringWithAttachment:starViewAsAttachment];

Now you got a NSAttributedString with the image you want just like a normal word in it. To insert it anywhere in a text, you just play with it like a normal String.

Here is my complete code, here I only attaching the image to the end of the text. However this technique can be apply to any place inside a text.

1
2
3
4
5
6
7
8
9
10
11
NSTextAttachment *starViewAsAttachment = [[NSTextAttachment alloc] init];
    starViewAsAttachment.image = [UIImage imageNamed:@"Your image"];
    NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:starViewAsAttachment];
    NSMutableAttributedString *hotelName= [[NSMutableAttributedString alloc] initWithString: viewModel.hotelDisplayName];
    /** 
     * Here I'm adding a space after NSTextAttachment just to make sure that
     * the NSTextAttachment will auto change to next line like normal text does.
     * Otherwise the NSTextAttachment does not move to the new line.
     */
    [hotelName appendAttributedString: [[NSAttributedString alloc] initWithString:@" "]];
    self.myUILabel.attributedText = hotelName;

A Little Bit More

Notice that in my case, Stars are one component but not individual separated elements. That being said, no matter how many stars it has, either 3 stars, 4 stars or 4.5 stars, it’s all just one image. The reason that I made it in one image is not that we can’t use multiple NSTextAttachment in a text, but if we do, each NSTextAttachment will be treated as different words. Therefor it may results in situation that the stars might be separated into two different lines when the space is not enough. But that’s not what we want, we want all stars in the same line, either at the current line if there’s enough space or next line if there’s no enough space.

While as we all know, the raw image for it will only include one star, and it’s suppose for us to use to build multiple stars. And the following is how I build this component based on a single image. The component itself is a subclass of UIView. And you got to find yourself a way to turn a UIView into a UIImage, it’s not hard.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)buildViewBasedOnStarNumber {
    NSUInteger starCount = [self.starNumber integerValue];
    BOOL isHalfStar = ([self.starNumber floatValue] - starCount) > 0 ? YES : NO;

    UIImageView *star1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed: @"ic_star"]];
    CGFloat starWidth = CGRectGetWidth(star1.bounds);
    CGFloat starHeight = CGRectGetHeight(star1.bounds);
    [self setFrame:CGRectMake(0, 0, starWidth * (isHalfStar ? starCount + 1 : starCount), starHeight)];

    for (int n = 0; n < starCount; n++) {
        UIImageView *star = [[UIImageView alloc] initWithImage:[UIImage imageNamed: @"ic_star"]];
        [star setFrame:CGRectMake(starWidth * n, 0, starWidth, starHeight)];
        [self addSubview:star];
    }

    if (isHalfStar) {
        UIImageView *halfStar = [[UIImageView alloc] initWithImage:[UIImage imageNamed: @"ic_star_half"]];
        [halfStar setFrame:CGRectMake(starWidth * starCount, 0, starWidth, starHeight)];
        [self addSubview:halfStar];
    }
}

Comments