-
Notifications
You must be signed in to change notification settings - Fork 5
/
AttrTextView copy.swift
138 lines (112 loc) · 6.77 KB
/
AttrTextView copy.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//
// AttrTextView.swift
// Norae
//
// Created by Eliot Han on 1/3/17.
// Copyright © 2017 Eliot Han. All rights reserved.
//
import UIKit
enum wordType{
case hashtag
case mention
}
//A custom text view that allows hashtags and @ symbols to be separated from the rest of the text and triggers actions upon selection
class AttrTextView: UITextView {
var textString: NSString?
var attrString: NSMutableAttributedString?
var callBack: ((String, wordType) -> Void)?
public func setText(text: String, withHashtagColor hashtagColor: UIColor, andMentionColor mentionColor: UIColor, andCallBack callBack: @escaping (String, wordType) -> Void, normalFont: UIFont, hashTagFont: UIFont, mentionFont: UIFont) {
self.callBack = callBack
self.attrString = NSMutableAttributedString(string: text)
self.textString = NSString(string: text)
// Set initial font attributes for our string
attrString?.addAttribute(NSFontAttributeName, value: normalFont, range: NSRange(location: 0, length: (textString?.length)!))
attrString?.addAttribute(NSForegroundColorAttributeName, value: UIColor.white, range: NSRange(location: 0, length: (textString?.length)!))
// Call a custom set Hashtag and Mention Attributes Function
setAttrWithName(attrName: "Hashtag", wordPrefix: "#", color: hashtagColor, text: text, font: hashTagFont)
setAttrWithName(attrName: "Mention", wordPrefix: "@", color: mentionColor, text: text, font: mentionFont)
// Add tap gesture that calls a function tapRecognized when tapped
let tapper = UITapGestureRecognizer(target: self, action: #selector(self.tapRecognized(tapGesture:)))
addGestureRecognizer(tapper)
}
private func setAttrWithName(attrName: String, wordPrefix: String, color: UIColor, text: String, font: UIFont) {
//Words can be separated by either a space or a line break
let charSet = CharacterSet(charactersIn: " \n")
let words = text.components(separatedBy: charSet)
//Filter to check for the # or @ prefix
for word in words.filter({$0.hasPrefix(wordPrefix)}) {
let range = textString!.range(of: word)
attrString?.addAttribute(NSForegroundColorAttributeName, value: color, range: range)
attrString?.addAttribute(attrName, value: 1, range: range)
attrString?.addAttribute("Clickable", value: 1, range: range)
attrString?.addAttribute(NSFontAttributeName, value: font, range: range)
}
self.attributedText = attrString
}
func tapRecognized(tapGesture: UITapGestureRecognizer) {
var wordString: String? // The String value of the word to pass into callback function
var char: NSAttributedString! //The character the user clicks on. It is non optional because if the user clicks on nothing, char will be a space or " "
var word: NSAttributedString? //The word the user clicks on
var isHashtag: AnyObject?
var isAtMention: AnyObject?
// Gets the range of the character at the place the user taps
let point = tapGesture.location(in: self)
let charPosition = closestPosition(to: point)
let charRange = tokenizer.rangeEnclosingPosition(charPosition!, with: .character, inDirection: 1)
//Checks if the user has tapped on a character.
if charRange != nil {
let location = offset(from: beginningOfDocument, to: charRange!.start)
let length = offset(from: charRange!.start, to: charRange!.end)
let attrRange = NSMakeRange(location, length)
char = attributedText.attributedSubstring(from: attrRange)
//If the user has not clicked on anything, exit the function
if char.string == " "{
print("User clicked on nothing")
return
}
// Checks the character's attribute, if any
isHashtag = char?.attribute("Hashtag", at: 0, longestEffectiveRange: nil, in: NSMakeRange(0, char!.length)) as AnyObject?
isAtMention = char?.attribute("Mention", at: 0, longestEffectiveRange: nil, in: NSMakeRange(0, char!.length)) as AnyObject?
}
// Gets the range of the word at the place user taps
let wordRange = tokenizer.rangeEnclosingPosition(charPosition!, with: .word, inDirection: 1)
/*
Check if wordRange is nil or not. The wordRange is nil if:
1. The User clicks on the "#" or "@"
2. The User has not clicked on anything. We already checked whether or not the user clicks on nothing so 1 is the only possibility
*/
if wordRange != nil{
// Get the word. This will not work if the char is "#" or "@" ie, if the user clicked on the # or @ in front of the word
let wordLocation = offset(from: beginningOfDocument, to: wordRange!.start)
let wordLength = offset(from: wordRange!.start, to: wordRange!.end)
let wordAttrRange = NSMakeRange(wordLocation, wordLength)
word = attributedText.attributedSubstring(from: wordAttrRange)
wordString = word!.string
}else{
/*
Because the user has clicked on the @ or # in front of the word, word will be nil as
tokenizer.rangeEnclosingPosition(charPosition!, with: .word, inDirection: 1) does not work with special characters.
What I am doing here is modifying the x position of the point the user taps the screen. Moving it to the right by about 12 points will move the point where we want to detect for a word, ie to the right of the # or @ symbol and onto the word's text
*/
var modifiedPoint = point
modifiedPoint.x += 12
let modifiedPosition = closestPosition(to: modifiedPoint)
let modifedWordRange = tokenizer.rangeEnclosingPosition(modifiedPosition!, with: .word, inDirection: 1)
if modifedWordRange != nil{
let wordLocation = offset(from: beginningOfDocument, to: modifedWordRange!.start)
let wordLength = offset(from: modifedWordRange!.start, to: modifedWordRange!.end)
let wordAttrRange = NSMakeRange(wordLocation, wordLength)
word = attributedText.attributedSubstring(from: wordAttrRange)
wordString = word!.string
}
}
if let stringToPass = wordString{
// Runs callback function if word is a Hashtag or Mention
if isHashtag != nil && callBack != nil {
callBack!(stringToPass, wordType.hashtag)
} else if isAtMention != nil && callBack != nil {
callBack!(stringToPass, wordType.mention)
}
}
}
}