Search code examples
iosios6uilabelnsattributedstring

Top-aligning text of different sizes within a UILabel


How to top-align text of different sizes within a UILabel? An example is top-aligning smaller-sized cent amount with larger-sized dollar amount in price banners.

Sample Image

UILabel in iOS6 supports NSAttributedStringwhich allows me to have text of different sizes in the same UILabel. However it doesn't seem to have an attribute for top-aligning text. What are the options to implement this? It seems to me that providing a custom drawing logic to do top-alignment based on a custom attributed string key might be best but I have no idea how to go about it.


Solution

  • I was able to achieve your desired result using a single label.

    Using a little math you can offset the baseline of the smaller text to achieve your desired result.

    Objective-C

    - (NSMutableAttributedString *)styleSalePriceLabel:(NSString *)salePrice withFont:(UIFont *)font
    {
        if ([salePrice rangeOfString:@"."].location == NSNotFound) {
            return [[NSMutableAttributedString alloc] initWithString:salePrice];
        } else {
            NSRange range = [salePrice rangeOfString:@"."];
            range.length = (salePrice.length - range.location);
            NSMutableAttributedString *stylizedPriceLabel = [[NSMutableAttributedString alloc] initWithString:salePrice];
            UIFont *smallFont = [UIFont fontWithName:font.fontName size:(font.pointSize / 2)];
            NSNumber *offsetAmount = @(font.capHeight - smallFont.capHeight);
            [stylizedPriceLabel addAttribute:NSFontAttributeName value:smallFont range:range];
            [stylizedPriceLabel addAttribute:NSBaselineOffsetAttributeName value:offsetAmount range:range];
            return stylizedPriceLabel;
        }
    }
    

    Swift

    extension Range where Bound == String.Index {
        func asNSRange() -> NSRange {
            let location = self.lowerBound.encodedOffset
            let length = self.lowerBound.encodedOffset - self.upperBound.encodedOffset
            return NSRange(location: location, length: length)
        }
    }
    
    extension String {
        func asStylizedPrice(using font: UIFont) -> NSMutableAttributedString {
            let stylizedPrice = NSMutableAttributedString(string: self, attributes: [.font: font])
    
            guard var changeRange = self.range(of: ".")?.asNSRange() else {
                return stylizedPrice
            }
    
            changeRange.length = self.count - changeRange.location
            // forgive the force unwrapping
            let changeFont = UIFont(name: font.fontName, size: (font.pointSize / 2))!
            let offset = font.capHeight - changeFont.capHeight
            stylizedPrice.addAttribute(.font, value: changeFont, range: changeRange)
            stylizedPrice.addAttribute(.baselineOffset, value: offset, range: changeRange)
            return stylizedPrice
        }
    }
    

    This yields the following: