finding location of specific characters in UILabel on iPhone
Asked Answered
J

3

6

I have a UILabel with some text, say "Hello World abcdefg" The label can have multiple lines, different font sizes etc.

Question: How do I find the coordinates of all letters "d" in this UILabel.

Logical first step is find the position of those characters in the string (UILabel.text), but then how do I translate that into coordinates when it's actually drawn on screen

The idea is to find those coordinates and draw something custom on top of that character (basically to cover it with a custom image)

Jodee answered 7/7, 2010 at 0:6 Comment(1)
This other answer provides part of what you will probably need.Hertford
Z
3

The basic tools for measuring text on iPhone are in UIStringDrawing.h but none of them do what you need. You will basically have to iterate through substrings one character at a time measuring each. When a line wraps (the result is taller), split after the last character that did not wrap and add the line height to your y coordinate.

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode;
Zerla answered 7/7, 2010 at 1:3 Comment(1)
sigh that's gonna be a bit of work, but I guess that's the best bet. Thanks for the reply!Jodee
L
0

Methods have changed since iOS 7.0 came out. Try this

- (CGFloat)charactersOffsetBeforeDayPartOfLabel {
    NSRange range = [[self stringFromDate:self.currentDate] rangeOfString:[NSString stringWithFormat:@"%i",[self dayFromDate:self.currentDate]]];
    NSString *chars = [[self stringFromDate:self.currentDate] substringToIndex:range.location];
    NSMutableArray *arrayOfChars = [[NSMutableArray alloc]init];
    [chars enumerateSubstringsInRange:NSMakeRange(0, [chars length]) options:(NSStringEnumerationByComposedCharacterSequences) usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
        [arrayOfChars addObject:substring];
    }];
    CGFloat charsOffsetTotal = 0;
    for (NSString *i in arrayOfChars){
        NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"Helvetica Neue" size:16.0f]};
        charsOffsetTotal += [i sizeWithAttributes:attributes].width;
    }
    return charsOffsetTotal;
}
Lummox answered 17/3, 2014 at 12:41 Comment(1)
Why would you calculate the size of each individual character? Why not simply get the size of the whole substring at once?Trilbee
A
0

Here ya go:

fileprivate let selfSizing = UILabel()

class DualColorLabel: UILabel
{
    var filled: UIColor?
    var unfilled: UIColor?
    var origin: String?
    var widths: [CGFloat] = []
    var fuckupLockup = false
    override var text: String? {
        didSet {
            if fuckupLockup {
                print ("SDBOFLAG-13822 wtf?")
            }
        }
    }
    func setupColorsAndText(filled: UIColor,
                    unfilled: UIColor)
    {
        self.filled = filled
        self.unfilled = unfilled
        guard let text = origin, text.count > 0 else {
            assertionFailure("usage error")
            return
        }
        guard font != nil else {
            usageError()
            return
        }
        for index in 1...text.count {
            let s = String.Index(utf16Offset: 0, in: text)
            let e = String.Index(utf16Offset: index, in: text)
            let beginning = text[s..<e]
            let p = String(beginning)
            
            selfSizing.font = font
            selfSizing.text = p
            let size = selfSizing.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
            let width = size.width
            widths.append(width)
        }
    }
    
    func setupfill(adjusted: CGRect)
    {
        assert(adjusted.origin.x <= 0, "fixed this code for fill in the middle: currently supported only fill at start")
        let endOffset = adjusted.width + adjusted.origin.x
        guard let font = self.font else {
            usageError()
            return
        }
        guard let origin = origin, let filled = filled,
              let unfilled = unfilled else {
            usageError()
            return
        }
        var idx = String.Index(utf16Offset: origin.count, in: origin)
        for (index, width) in widths.enumerated() {
            if endOffset < width {
                idx = String.Index(utf16Offset: index, in: origin)
                print ("SDBOFLAG-13822 index \(index) for text \(origin)")
                break
            }
        }
        let total = NSMutableAttributedString()
        do {
            let s = String.Index(utf16Offset: 0, in: origin)
            let beginning = origin[s..<idx]
            let p = String(beginning)
            print("SDBOFLAG-13822 filled text \(p)")
            let filledAttributes:
                [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor:
//                                                    UIColor.yellow,
                                                    filled,
                                                  NSAttributedString.Key.font:
                                                    font
                ]
            
            let filledPortion = NSAttributedString(string: p, attributes: filledAttributes)
            total.append(filledPortion)
        }
        let unfilledAttributes:
            [NSAttributedString.Key : Any] = [NSAttributedString.Key.foregroundColor:
//                                                UIColor.blue,
                                              unfilled,
                                              NSAttributedString.Key.font: font]
        let e = String.Index(utf16Offset: origin.count, in: origin)
        let ending = origin[idx..<e]
        let str = String(ending)
        print("SDBOFLAG-13822 unfilled text \(str)")
        let unfilledPortion = NSAttributedString(string: str, attributes: unfilledAttributes)
        total.append(unfilledPortion)

        self.attributedText = total
        fuckupLockup = true
    }
    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}

func usageError()
{
    assertionFailure("usage error")
}

The width calculation for fragments goes into widths array per suggestions provided.

Auguste answered 10/11, 2020 at 15:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.