Last active
November 23, 2016 14:00
-
-
Save phlippieb/f0c5e6074e6eebbec7b7f34b44630314 to your computer and use it in GitHub Desktop.
A UITextView subclass that allows clients to register ranges of characters as "tappable", with keys to identify those ranges. A delegate can be assigned to receive callbacks when tappable ranges are tapped. When a user taps on a character in the text view which is in a registered range (with a registered key), the delegate's callback is called w…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// RangeTappableTextView.swift | |
// Kalido | |
// | |
// Created by Phlippie Bosman on 2016/11/23. | |
// Copyright © 2016 Kalido. All rights reserved. | |
// | |
protocol RangeTappableTextViewDelegate: class { | |
func rangeTappableTextView(_ rangeTappableTextView: RangeTappableTextView, | |
didTapCharacterInRange range: Range<Int>, | |
withKey key: Int) | |
} | |
class RangeTappableTextView: UITextView { | |
weak open var rangeTappedDelegate: RangeTappableTextViewDelegate? | |
fileprivate var tappableRanges: [Range<Int>] = [] | |
fileprivate var keys: [Int] = [] | |
/* | |
Registers a range that will trigger the delegate if tapped. | |
If a user taps a character covered by multiple ranges, the most recently-added range and key will be passed to the delegate. | |
*/ | |
open func registerTappableRange(_ range: Range<Int>, withKey key: Int) { | |
tappableRanges.insert(range, at: 0) | |
keys.insert(key, at: 0) | |
} | |
open func unregister(range: Range<Int>) { | |
unregisterIfFound(index: tappableRanges.index(of: range)) | |
} | |
open func unregister(key: Int) { | |
unregisterIfFound(index: keys.index(of: key)) | |
} | |
private func unregisterIfFound(index: Int?) { | |
if let index = index { | |
tappableRanges.remove(at: index) | |
keys.remove(at: index) | |
} | |
} | |
open func unregisterAll() { | |
tappableRanges.removeAll() | |
keys.removeAll() | |
} | |
} | |
extension RangeTappableTextView { | |
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { | |
if let tapRecognizer = gestureRecognizer as? UITapGestureRecognizer { | |
var location = tapRecognizer.location(in: self) | |
location.x -= contentInset.left | |
location.y -= contentInset.top | |
let charIndex = layoutManager.characterIndex( | |
for: location, | |
in: textContainer, | |
fractionOfDistanceBetweenInsertionPoints: nil) | |
for (range, key) in zip(tappableRanges, keys) { | |
if range.contains(charIndex) { | |
rangeTappedDelegate?.rangeTappableTextView(self, didTapCharacterInRange: range, withKey: key) | |
break | |
} | |
} | |
} | |
return super.gestureRecognizerShouldBegin(gestureRecognizer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment