kemchenj
12/22/2017 - 10:07 AM

UITextView+CorrectSelection

extension UITextView {

    func correctSelection() {
        let currentSelectedRange = selectedRange
        let wholeRange = attributedText.wholeRange

        let emptyRange = NSRange()
        var lowerAttributeEffectiveRange = NSRange()
        var upperAttributeEffectiveRange = NSRange()

        let validRange = NSRange(
            location: wholeRange.lowerBound + 1,
            length: wholeRange.length - 2
        )

        let lowerBindingText: String? = {
            var text: String? = nil
            if validRange.contains(currentSelectedRange.lowerBound) {
                text = attributedText.attribute(
                    NSAttributedStringKey.bindingText,
                    at: currentSelectedRange.lowerBound,
                    longestEffectiveRange: &lowerAttributeEffectiveRange,
                    in: wholeRange
                ) as? String
            }
            return text
        }()
        if lowerBindingText == nil {
            lowerAttributeEffectiveRange = emptyRange
        }

        let upperBindingText: String? = {
            var text: String? = nil
            if validRange.contains(currentSelectedRange.upperBound - 1) {
                text = attributedText.attribute(
                    NSAttributedStringKey.bindingText,
                    at: currentSelectedRange.upperBound - 1,
                    longestEffectiveRange: &upperAttributeEffectiveRange,
                    in: wholeRange
                ) as? String
            }
            return text
        }()
        if upperBindingText == nil {
            upperAttributeEffectiveRange = emptyRange
        }

        guard currentSelectedRange.upperBound != lowerAttributeEffectiveRange.lowerBound else { return }

        switch (lowerAttributeEffectiveRange, upperAttributeEffectiveRange) {
        case (emptyRange, emptyRange):
            break
        case (let effectiveRange, emptyRange), (emptyRange, let effectiveRange):
            selectedRange = currentSelectedRange
                .union(effectiveRange)
        case (_, _):
            selectedRange = currentSelectedRange
                .union(lowerAttributeEffectiveRange)
                .union(upperAttributeEffectiveRange)
        }
    }
}