Skip to content

Instantly share code, notes, and snippets.

@phlippieb
Last active November 23, 2016 14:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phlippieb/f0c5e6074e6eebbec7b7f34b44630314 to your computer and use it in GitHub Desktop.
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…
//
// 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