NSForegroundColorAttributeName for UILabel using N

2020-04-18 07:03发布

问题:

I have NSAttributed string with links in it and I want to load it inside UILabel. I works fine, however all links are blue Color.

let string = NSMutableAttributedString(attributedString: attributedText)
string.addAttributes([NSForegroundColorAttributeName:linkColor], range: linkRange)
self.attributedText = string

No change to foreground color, setting all other attributes work, like strikethrough style. Just link always stays blue.

NSAttributed string is generated from HTML if that makes any difference.

回答1:

Ended up doing

class AttributedTextLabel:UILabel {

var attributedString:NSAttributedString?{
    didSet{
        guard let attributedString = attributedString else {
            return
        }
        let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)

        mutableAttributedString.enumerateAttribute(NSLinkAttributeName, inRange: NSRange(location: 0, length: attributedString.length), options: NSAttributedStringEnumerationOptions.Reverse) {[weak self] (attribute, range, other) in

            if let url = attribute as? NSURL {
                mutableAttributedString.removeAttribute(NSLinkAttributeName, range: range)
                self?.links.append(Link(url: url, range: range))
            }
        }
        self.attributedText = mutableAttributedString

    }
}

struct Link {
    var url:NSURL
    var range:NSRange
}

var links:[Link] = []

var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero

private var textContentSize:CGSize {
    let textContainerWidth = frame.width - edgeInsets.left - edgeInsets.right
    let textContainerHeight = frame.height - edgeInsets.top - edgeInsets.bottom

    return CGSizeMake(textContainerWidth, textContainerHeight)
}


func characterIndexAtPoint(point:CGPoint) -> Int? {
    guard let attributedText = attributedText else {
        return nil
    }

    let layoutManager = NSLayoutManager()
    let textContainer = NSTextContainer(size: textContentSize)

    textContainer.lineFragmentPadding = 0.0
    textContainer.lineBreakMode = self.lineBreakMode
    textContainer.maximumNumberOfLines = self.numberOfLines
    layoutManager.addTextContainer(textContainer)

    let storage = NSTextStorage(attributedString: attributedText)
    storage.addLayoutManager(layoutManager)
    let adjustedPoint = CGPointMake(point.x-edgeInsets.left, point.y-edgeInsets.top)

    let characterIndex = layoutManager.characterIndexForPoint(point, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

    return characterIndex
}

override func drawTextInRect(rect: CGRect) {
    return super.drawTextInRect(UIEdgeInsetsInsetRect(rect, edgeInsets))
}

private var selectedRange:NSRange?

private var highligtedLink:Link? {
    didSet{
        let string = self.attributedText as! NSMutableAttributedString
        if let oldValue = oldValue {
            if let selectedLinkColor = NativeTextKit.TextAttributes.selectedLinkColor.value {
                string.addAttributes([
                    NSForegroundColorAttributeName:selectedLinkColor
                    ], range: oldValue.range)
            }
        }

        if let highligtedLink = highligtedLink {
            if let selectedLinkColor = NativeTextKit.TextAttributes.selectedLinkColor.value {
                string.addAttributes([
                    NSForegroundColorAttributeName:selectedLinkColor
                    ], range: highligtedLink.range)
            }
        }

        self.attributedText = string
    }
}

override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    guard let touch = touches.first else {
        return
    }
    let char = characterIndexAtPoint(touch.locationInView(self))
    let string = self.attributedText as! NSMutableAttributedString

    highligtedLink = linkForTouch(touch)

    string.addAttributes([
        NSForegroundColorAttributeName:UIColor.brownColor()
        ], range: NSMakeRange(char!, 1))

    attributedText = string
}

func linkForTouch(touch:UITouch)->Link? {
    guard let attributedText = attributedText else {
        return nil
    }
    guard let characterIndex = characterIndexAtPoint(touch.locationInView(self)) else {
        return nil
    }
    return links.filter({NSLocationInRange(characterIndex, $0.range)}).first
}

override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
    highligtedLink = nil
}

override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    guard let touch = touches.first else {
        return
    }
    if let highligtedLink = highligtedLink, let lastTouchedLink = linkForTouch(touch) where highligtedLink.url == lastTouchedLink.url {
        urlInteractionHandler?(textView: UITextView(), url:lastTouchedLink.url)
    }

}

/// Executed on link interaction
var urlInteractionHandler:URLInteractionHandler?
}

Does the job, took a while to figure out. Because UILabel has its own link formatting ended up

  • Remove all links from attributed string once string is set
  • Add links and ranges to array
  • After link is selected use NSTextContainer to figure out what index the character was
  • Find range that character belongs to
  • Return link