More Swifty NSAttributedString Attribute Query Methods

NSAttributedString API takes an NSRange by-reference. That’s cumbersome to use, though, because you need to initialize a non-nil range, and there you should initialize it with NSNotFound to indicate an illegal state. Afterwards, you need to check if the range changed to a legal value.

A more Swift-y approach is to use tuples:

extension NSAttributedString {
    /// Wrapper for `attribute(_:at:effectiveRange:)` returning value and range.
    /// - Returns: Tuple of the attribute value and effective range in which
    ///   `attributeName` applies around `location`. `nil` if no match is found or
    ///   the range lookup failed.
    func attribute(_ attributeName: NSAttributedString.Key,
                   at location: Int
    ) -> (value: Any, effectiveRange: NSRange)? {
        var range: NSRange = NSRange(location: NSNotFound, length: 0)
        guard let value = attribute(attributeName, at: location, effectiveRange: &range),
              range.location != NSNotFound
        else { return nil }
        return (value, range)
    }

    /// Wrapper for the range result of `attribute(_:at:effectiveRange:)`.
    /// - Returns: Effective range in which `attributeName` applies
    //    around `location`. `nil` if no match is found.
    func effectiveAttributeRange(attributeName: NSAttributedString.Key,
                                 at location: Int) -> NSRange? {
        return attribute(attributeName, at: location)?.range
    }
}

This is hardly mind-blowing, but I lived without this for 5 years or so and now I’m fed up. :) Might as well share the result, right?