MessageLabel.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. /*
  2. MIT License
  3. Copyright (c) 2017-2018 MessageKit
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in all
  11. copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  18. SOFTWARE.
  19. */
  20. import UIKit
  21. open class MessageLabel: UILabel {
  22. // MARK: - Private Properties
  23. private lazy var layoutManager: NSLayoutManager = {
  24. let layoutManager = NSLayoutManager()
  25. layoutManager.addTextContainer(self.textContainer)
  26. return layoutManager
  27. }()
  28. private lazy var textContainer: NSTextContainer = {
  29. let textContainer = NSTextContainer()
  30. textContainer.lineFragmentPadding = 0
  31. textContainer.maximumNumberOfLines = self.numberOfLines
  32. textContainer.lineBreakMode = self.lineBreakMode
  33. textContainer.size = self.bounds.size
  34. return textContainer
  35. }()
  36. private lazy var textStorage: NSTextStorage = {
  37. let textStorage = NSTextStorage()
  38. textStorage.addLayoutManager(self.layoutManager)
  39. return textStorage
  40. }()
  41. private lazy var rangesForDetectors: [DetectorType: [(NSRange, MessageTextCheckingType)]] = [:]
  42. private var isConfiguring: Bool = false
  43. // MARK: - Public Properties
  44. open weak var delegate: MessageLabelDelegate?
  45. open var enabledDetectors: [DetectorType] = [] {
  46. didSet {
  47. setTextStorage(attributedText, shouldParse: true)
  48. }
  49. }
  50. open override var attributedText: NSAttributedString? {
  51. didSet {
  52. setTextStorage(attributedText, shouldParse: true)
  53. }
  54. }
  55. open override var text: String? {
  56. didSet {
  57. setTextStorage(attributedText, shouldParse: true)
  58. }
  59. }
  60. open override var font: UIFont! {
  61. didSet {
  62. setTextStorage(attributedText, shouldParse: false)
  63. }
  64. }
  65. open override var textColor: UIColor! {
  66. didSet {
  67. setTextStorage(attributedText, shouldParse: false)
  68. }
  69. }
  70. open override var lineBreakMode: NSLineBreakMode {
  71. didSet {
  72. textContainer.lineBreakMode = lineBreakMode
  73. if !isConfiguring { setNeedsDisplay() }
  74. }
  75. }
  76. open override var numberOfLines: Int {
  77. didSet {
  78. textContainer.maximumNumberOfLines = numberOfLines
  79. if !isConfiguring { setNeedsDisplay() }
  80. }
  81. }
  82. open override var textAlignment: NSTextAlignment {
  83. didSet {
  84. setTextStorage(attributedText, shouldParse: false)
  85. }
  86. }
  87. open var textInsets: UIEdgeInsets = .zero {
  88. didSet {
  89. if !isConfiguring { setNeedsDisplay() }
  90. }
  91. }
  92. internal var messageLabelFont: UIFont?
  93. private var attributesNeedUpdate = false
  94. public static var defaultAttributes: [NSAttributedString.Key: Any] = {
  95. return [
  96. NSAttributedString.Key.foregroundColor: UIColor.darkText,
  97. NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
  98. NSAttributedString.Key.underlineColor: UIColor.darkText
  99. ]
  100. }()
  101. open internal(set) var addressAttributes: [NSAttributedString.Key: Any] = defaultAttributes
  102. open internal(set) var dateAttributes: [NSAttributedString.Key: Any] = defaultAttributes
  103. open internal(set) var phoneNumberAttributes: [NSAttributedString.Key: Any] = defaultAttributes
  104. open internal(set) var urlAttributes: [NSAttributedString.Key: Any] = defaultAttributes
  105. open internal(set) var transitInformationAttributes: [NSAttributedString.Key: Any] = defaultAttributes
  106. public func setAttributes(_ attributes: [NSAttributedString.Key: Any], detector: DetectorType) {
  107. switch detector {
  108. case .phoneNumber:
  109. phoneNumberAttributes = attributes
  110. case .address:
  111. addressAttributes = attributes
  112. case .date:
  113. dateAttributes = attributes
  114. case .url:
  115. urlAttributes = attributes
  116. case .transitInformation:
  117. transitInformationAttributes = attributes
  118. }
  119. if isConfiguring {
  120. attributesNeedUpdate = true
  121. } else {
  122. updateAttributes(for: [detector])
  123. }
  124. }
  125. // MARK: - Initializers
  126. public override init(frame: CGRect) {
  127. super.init(frame: frame)
  128. self.numberOfLines = 0
  129. self.lineBreakMode = .byWordWrapping
  130. }
  131. public required init?(coder aDecoder: NSCoder) {
  132. fatalError("init(coder:) has not been implemented")
  133. }
  134. // MARK: - Open Methods
  135. open override func drawText(in rect: CGRect) {
  136. let insetRect = rect.inset(by: textInsets)
  137. textContainer.size = CGSize(width: insetRect.width, height: rect.height)
  138. let origin = insetRect.origin
  139. let range = layoutManager.glyphRange(for: textContainer)
  140. layoutManager.drawBackground(forGlyphRange: range, at: origin)
  141. layoutManager.drawGlyphs(forGlyphRange: range, at: origin)
  142. }
  143. // MARK: - Public Methods
  144. public func configure(block: () -> Void) {
  145. isConfiguring = true
  146. block()
  147. if attributesNeedUpdate {
  148. updateAttributes(for: enabledDetectors)
  149. }
  150. attributesNeedUpdate = false
  151. isConfiguring = false
  152. setNeedsDisplay()
  153. }
  154. // MARK: - Private Methods
  155. private func setTextStorage(_ newText: NSAttributedString?, shouldParse: Bool) {
  156. guard let newText = newText, newText.length > 0 else {
  157. textStorage.setAttributedString(NSAttributedString())
  158. setNeedsDisplay()
  159. return
  160. }
  161. let style = paragraphStyle(for: newText)
  162. let range = NSRange(location: 0, length: newText.length)
  163. let mutableText = NSMutableAttributedString(attributedString: newText)
  164. mutableText.addAttribute(.paragraphStyle, value: style, range: range)
  165. if shouldParse {
  166. rangesForDetectors.removeAll()
  167. let results = parse(text: mutableText)
  168. setRangesForDetectors(in: results)
  169. }
  170. for (detector, rangeTuples) in rangesForDetectors {
  171. if enabledDetectors.contains(detector) {
  172. let attributes = detectorAttributes(for: detector)
  173. rangeTuples.forEach { (range, _) in
  174. mutableText.addAttributes(attributes, range: range)
  175. }
  176. }
  177. }
  178. let modifiedText = NSAttributedString(attributedString: mutableText)
  179. textStorage.setAttributedString(modifiedText)
  180. if !isConfiguring { setNeedsDisplay() }
  181. }
  182. private func paragraphStyle(for text: NSAttributedString) -> NSParagraphStyle {
  183. guard text.length > 0 else { return NSParagraphStyle() }
  184. var range = NSRange(location: 0, length: text.length)
  185. let existingStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: &range) as? NSMutableParagraphStyle
  186. let style = existingStyle ?? NSMutableParagraphStyle()
  187. style.lineBreakMode = lineBreakMode
  188. style.alignment = textAlignment
  189. return style
  190. }
  191. private func updateAttributes(for detectors: [DetectorType]) {
  192. guard let attributedText = attributedText, attributedText.length > 0 else { return }
  193. let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText)
  194. for detector in detectors {
  195. guard let rangeTuples = rangesForDetectors[detector] else { continue }
  196. for (range, _) in rangeTuples {
  197. let attributes = detectorAttributes(for: detector)
  198. mutableAttributedString.addAttributes(attributes, range: range)
  199. }
  200. let updatedString = NSAttributedString(attributedString: mutableAttributedString)
  201. textStorage.setAttributedString(updatedString)
  202. }
  203. }
  204. private func detectorAttributes(for detectorType: DetectorType) -> [NSAttributedString.Key: Any] {
  205. switch detectorType {
  206. case .address:
  207. return addressAttributes
  208. case .date:
  209. return dateAttributes
  210. case .phoneNumber:
  211. return phoneNumberAttributes
  212. case .url:
  213. return urlAttributes
  214. case .transitInformation:
  215. return transitInformationAttributes
  216. }
  217. }
  218. private func detectorAttributes(for checkingResultType: NSTextCheckingResult.CheckingType) -> [NSAttributedString.Key: Any] {
  219. switch checkingResultType {
  220. case .address:
  221. return addressAttributes
  222. case .date:
  223. return dateAttributes
  224. case .phoneNumber:
  225. return phoneNumberAttributes
  226. case .link:
  227. return urlAttributes
  228. case .transitInformation:
  229. return transitInformationAttributes
  230. default:
  231. fatalError(MessageKitError.unrecognizedCheckingResult)
  232. }
  233. }
  234. // MARK: - Parsing Text
  235. private func parse(text: NSAttributedString) -> [NSTextCheckingResult] {
  236. guard enabledDetectors.isEmpty == false else { return [] }
  237. let checkingTypes = enabledDetectors.reduce(0) { $0 | $1.textCheckingType.rawValue }
  238. let detector = try? NSDataDetector(types: checkingTypes)
  239. let range = NSRange(location: 0, length: text.length)
  240. return detector?.matches(in: text.string, options: [], range: range) ?? []
  241. }
  242. private func setRangesForDetectors(in checkingResults: [NSTextCheckingResult]) {
  243. guard checkingResults.isEmpty == false else { return }
  244. for result in checkingResults {
  245. switch result.resultType {
  246. case .address:
  247. var ranges = rangesForDetectors[.address] ?? []
  248. let tuple: (NSRange, MessageTextCheckingType) = (result.range, .addressComponents(result.addressComponents))
  249. ranges.append(tuple)
  250. rangesForDetectors.updateValue(ranges, forKey: .address)
  251. case .date:
  252. var ranges = rangesForDetectors[.date] ?? []
  253. let tuple: (NSRange, MessageTextCheckingType) = (result.range, .date(result.date))
  254. ranges.append(tuple)
  255. rangesForDetectors.updateValue(ranges, forKey: .date)
  256. case .phoneNumber:
  257. var ranges = rangesForDetectors[.phoneNumber] ?? []
  258. let tuple: (NSRange, MessageTextCheckingType) = (result.range, .phoneNumber(result.phoneNumber))
  259. ranges.append(tuple)
  260. rangesForDetectors.updateValue(ranges, forKey: .phoneNumber)
  261. case .link:
  262. var ranges = rangesForDetectors[.url] ?? []
  263. let tuple: (NSRange, MessageTextCheckingType) = (result.range, .link(result.url))
  264. ranges.append(tuple)
  265. rangesForDetectors.updateValue(ranges, forKey: .url)
  266. case .transitInformation:
  267. var ranges = rangesForDetectors[.transitInformation] ?? []
  268. let tuple: (NSRange, MessageTextCheckingType) = (result.range, .transitInfoComponents(result.components))
  269. ranges.append(tuple)
  270. rangesForDetectors.updateValue(ranges, forKey: .transitInformation)
  271. default:
  272. fatalError("Received an unrecognized NSTextCheckingResult.CheckingType")
  273. }
  274. }
  275. }
  276. // MARK: - Gesture Handling
  277. private func stringIndex(at location: CGPoint) -> Int? {
  278. guard textStorage.length > 0 else { return nil }
  279. var location = location
  280. location.x -= textInsets.left
  281. location.y -= textInsets.top
  282. let index = layoutManager.glyphIndex(for: location, in: textContainer)
  283. let lineRect = layoutManager.lineFragmentUsedRect(forGlyphAt: index, effectiveRange: nil)
  284. var characterIndex: Int?
  285. if lineRect.contains(location) {
  286. characterIndex = layoutManager.characterIndexForGlyph(at: index)
  287. }
  288. return characterIndex
  289. }
  290. internal func handleGesture(_ touchLocation: CGPoint) -> Bool {
  291. guard let index = stringIndex(at: touchLocation) else { return false }
  292. for (detectorType, ranges) in rangesForDetectors {
  293. for (range, value) in ranges {
  294. if range.contains(index) {
  295. handleGesture(for: detectorType, value: value)
  296. return true
  297. }
  298. }
  299. }
  300. return false
  301. }
  302. private func handleGesture(for detectorType: DetectorType, value: MessageTextCheckingType) {
  303. switch value {
  304. case let .addressComponents(addressComponents):
  305. var transformedAddressComponents = [String: String]()
  306. guard let addressComponents = addressComponents else { return }
  307. addressComponents.forEach { (key, value) in
  308. transformedAddressComponents[key.rawValue] = value
  309. }
  310. handleAddress(transformedAddressComponents)
  311. case let .phoneNumber(phoneNumber):
  312. guard let phoneNumber = phoneNumber else { return }
  313. handlePhoneNumber(phoneNumber)
  314. case let .date(date):
  315. guard let date = date else { return }
  316. handleDate(date)
  317. case let .link(url):
  318. guard let url = url else { return }
  319. handleURL(url)
  320. case let .transitInfoComponents(transitInformation):
  321. var transformedTransitInformation = [String: String]()
  322. guard let transitInformation = transitInformation else { return }
  323. transitInformation.forEach { (key, value) in
  324. transformedTransitInformation[key.rawValue] = value
  325. }
  326. handleTransitInformation(transformedTransitInformation)
  327. }
  328. }
  329. private func handleAddress(_ addressComponents: [String: String]) {
  330. delegate?.didSelectAddress(addressComponents)
  331. }
  332. private func handleDate(_ date: Date) {
  333. delegate?.didSelectDate(date)
  334. }
  335. private func handleURL(_ url: URL) {
  336. delegate?.didSelectURL(url)
  337. }
  338. private func handlePhoneNumber(_ phoneNumber: String) {
  339. delegate?.didSelectPhoneNumber(phoneNumber)
  340. }
  341. private func handleTransitInformation(_ components: [String: String]) {
  342. delegate?.didSelectTransitInformation(components)
  343. }
  344. }
  345. private enum MessageTextCheckingType {
  346. case addressComponents([NSTextCheckingKey: String]?)
  347. case date(Date?)
  348. case phoneNumber(String?)
  349. case link(URL?)
  350. case transitInfoComponents([NSTextCheckingKey: String]?)
  351. }