Guest User

ActiveLabel.swift

a guest
Dec 17th, 2015
386
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Swift 11.19 KB | None | 0 0
  1. //
  2. //  ActiveLabel.swift
  3. //  ActiveLabel
  4. //
  5. //  Created by Johannes Schickling on 9/4/15.
  6. //  Copyright © 2015 Optonaut. All rights reserved.
  7. //
  8.  
  9. import Foundation
  10. import UIKit
  11.  
  12. public class ActiveLabel: UILabel {
  13.    
  14.     // MARK: - public properties
  15.     public var mentionEnabled: Bool = true {
  16.         didSet {
  17.             updateTextStorage()
  18.         }
  19.     }
  20.     public var hashtagEnabled: Bool = true {
  21.         didSet {
  22.             updateTextStorage()
  23.         }
  24.     }
  25.     public var URLEnabled: Bool = true {
  26.         didSet {
  27.             updateTextStorage()
  28.         }
  29.     }
  30.     public var mentionColor: UIColor = .blueColor() {
  31.         didSet {
  32.             updateTextStorage()
  33.         }
  34.     }
  35.     public var mentionSelectedColor: UIColor? {
  36.         didSet {
  37.             updateTextStorage()
  38.         }
  39.     }
  40.     public var hashtagColor: UIColor = .blueColor() {
  41.         didSet {
  42.             updateTextStorage()
  43.         }
  44.     }
  45.     public var hashtagSelectedColor: UIColor? {
  46.         didSet {
  47.             updateTextStorage()
  48.         }
  49.     }
  50.     public var URLColor: UIColor = .blueColor() {
  51.         didSet {
  52.             updateTextStorage()
  53.         }
  54.     }
  55.     public var URLSelectedColor: UIColor? {
  56.         didSet {
  57.             updateTextStorage()
  58.         }
  59.     }
  60.     public var lineSpacing: Float? {
  61.         didSet {
  62.             updateTextStorage()
  63.         }
  64.     }
  65.    
  66.     // MARK: - public methods
  67.     public func handleMentionTap(handler: (String) -> ()) {
  68.         mentionTapHandler = handler
  69.     }
  70.    
  71.     public func handleHashtagTap(handler: (String) -> ()) {
  72.         hashtagTapHandler = handler
  73.     }
  74.    
  75.     public func handleURLTap(handler: (NSURL) -> ()) {
  76.         urlTapHandler = handler
  77.     }
  78.    
  79.     // MARK: - override UILabel properties
  80.     override public var text: String? {
  81.         didSet {
  82.             updateTextStorage()
  83.         }
  84.     }
  85.    
  86.     override public var attributedText: NSAttributedString? {
  87.         didSet {
  88.             updateTextStorage()
  89.         }
  90.     }
  91.    
  92.     override public var font: UIFont! {
  93.         didSet {
  94.             updateTextStorage()
  95.         }
  96.     }
  97.    
  98.     override public var textColor: UIColor! {
  99.         didSet {
  100.             updateTextStorage()
  101.         }
  102.     }
  103.    
  104.     // MARK: - init functions
  105.     override public init(frame: CGRect) {
  106.         super.init(frame: frame)
  107.        
  108.         setupLabel()
  109.     }
  110.    
  111.     required public init?(coder aDecoder: NSCoder) {
  112.         super.init(coder: aDecoder)
  113.        
  114.         setupLabel()
  115.     }
  116.    
  117.     public override func drawTextInRect(rect: CGRect) {
  118.         let range = NSRange(location: 0, length: textStorage.length)
  119.        
  120.         textContainer.size = rect.size
  121.        
  122.         layoutManager.drawBackgroundForGlyphRange(range, atPoint: rect.origin)
  123.         layoutManager.drawGlyphsForGlyphRange(range, atPoint: rect.origin)
  124.     }
  125.    
  126.     // MARK: - touch events
  127.     func onTouch(gesture: UILongPressGestureRecognizer) {
  128.         let location = gesture.locationInView(self)
  129.        
  130.         switch gesture.state {
  131.         case .Began, .Changed:
  132.             if let element = elementAtLocation(location) {
  133.                 if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length {
  134.                     updateAttributesWhenSelected(false)
  135.                     selectedElement = element
  136.                     updateAttributesWhenSelected(true)
  137.                 }
  138.             } else {
  139.                 updateAttributesWhenSelected(false)
  140.                 selectedElement = nil
  141.             }
  142.         case .Cancelled, .Ended:
  143.             guard let selectedElement = selectedElement else {
  144.                 return
  145.             }
  146.            
  147.             switch selectedElement.element {
  148.             case .Mention(let userHandle): mentionTapHandler?(userHandle)
  149.             case .Hashtag(let hashtag): hashtagTapHandler?(hashtag)
  150.             case .URL(let url): urlTapHandler?(url)
  151.             case .None: ()
  152.             }
  153.            
  154.             let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC)))
  155.             dispatch_after(when, dispatch_get_main_queue()) {
  156.                 self.updateAttributesWhenSelected(false)
  157.                 self.selectedElement = nil
  158.             }
  159.         default: ()
  160.         }
  161.     }
  162.    
  163.     // MARK: - private properties
  164.     private var mentionTapHandler: ((String) -> ())?
  165.     private var hashtagTapHandler: ((String) -> ())?
  166.     private var urlTapHandler: ((NSURL) -> ())?
  167.    
  168.     private var selectedElement: (range: NSRange, element: ActiveElement)?
  169.     private lazy var textStorage = NSTextStorage()
  170.     private lazy var layoutManager = NSLayoutManager()
  171.     private lazy var textContainer = NSTextContainer()
  172.     private lazy var activeElements: [ActiveType: [(range: NSRange, element: ActiveElement)]] = [
  173.         .Mention: [],
  174.         .Hashtag: [],
  175.         .URL: [],
  176.     ]
  177.    
  178.     // MARK: - helper functions
  179.     private func setupLabel() {
  180.         textStorage.addLayoutManager(layoutManager)
  181.         layoutManager.addTextContainer(textContainer)
  182.         textContainer.lineFragmentPadding = 0
  183.        
  184.         let touchRecognizer = UILongPressGestureRecognizer(target: self, action: "onTouch:")
  185.         touchRecognizer.minimumPressDuration = 0.00001
  186.         touchRecognizer.cancelsTouchesInView = false
  187.         touchRecognizer.delegate = self
  188.         addGestureRecognizer(touchRecognizer)
  189.        
  190.         userInteractionEnabled = true
  191.     }
  192.    
  193.     private func updateTextStorage() {
  194.         guard let attributedText = attributedText else {
  195.             return
  196.         }
  197.        
  198.         // clean up previous active elements
  199.         for (type, _) in activeElements {
  200.             activeElements[type]?.removeAll()
  201.         }
  202.        
  203.         guard attributedText.length > 0 else {
  204.             return
  205.         }
  206.        
  207.         let mutAttrString = addLineBreak(attributedText)
  208.         parseTextAndExtractActiveElements(mutAttrString)
  209.         addLinkAttribute(mutAttrString)
  210.        
  211.         textStorage.setAttributedString(mutAttrString)
  212.        
  213.         setNeedsDisplay()
  214.     }
  215.    
  216.     /// add link attribute
  217.     private func addLinkAttribute(mutAttrString: NSMutableAttributedString) {
  218.         var range = NSRange(location: 0, length: 0)
  219.         var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range)
  220.        
  221.         attributes[NSFontAttributeName] = font!
  222.         attributes[NSForegroundColorAttributeName] = textColor
  223.         mutAttrString.addAttributes(attributes, range: range)
  224.        
  225.         attributes[NSForegroundColorAttributeName] = mentionColor
  226.        
  227.         for (type, elements) in activeElements {
  228.            
  229.             switch type {
  230.             case .Mention: attributes[NSForegroundColorAttributeName] = mentionColor
  231.             case .Hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor
  232.             case .URL: attributes[NSForegroundColorAttributeName] = URLColor
  233.             case .None: ()
  234.             }
  235.            
  236.             for element in elements {
  237.                 mutAttrString.setAttributes(attributes, range: element.range)
  238.             }
  239.         }
  240.     }
  241.    
  242.     /// use regex check all link ranges
  243.     private func parseTextAndExtractActiveElements(attrString: NSAttributedString) {
  244.         let textString = attrString.string as NSString
  245.         for word in textString.componentsSeparatedByString(" ") {
  246.             let element = activeElement(word)
  247.             switch element {
  248.             case .Mention(let userHandle) where mentionEnabled:
  249.                 activeElements[.Mention]?.append((textString.rangeOfString("@\(userHandle)"), element))
  250.             case .Hashtag(let hashtag) where hashtagEnabled:
  251.                 activeElements[.Hashtag]?.append((textString.rangeOfString("#\(hashtag)"), element))
  252.             case .URL(let url) where URLEnabled:
  253.                 activeElements[.URL]?.append((textString.rangeOfString(url.absoluteString), element))
  254.             default: ()
  255.             }
  256.         }
  257.     }
  258.    
  259.     /// add line break mode
  260.     private func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString {
  261.         let mutAttrString = NSMutableAttributedString(attributedString: attrString)
  262.        
  263.         var range = NSRange(location: 0, length: 0)
  264.         var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range)
  265.        
  266.         let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle()
  267.         paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping
  268.         if let lineSpacing = lineSpacing {
  269.             paragraphStyle.lineSpacing = CGFloat(lineSpacing)
  270.         }
  271.        
  272.         attributes[NSParagraphStyleAttributeName] = paragraphStyle
  273.         mutAttrString.setAttributes(attributes, range: range)
  274.        
  275.         return mutAttrString
  276.     }
  277.    
  278.     private func updateAttributesWhenSelected(isSelected: Bool) {
  279.         guard let selectedElement = selectedElement else {
  280.             return
  281.         }
  282.        
  283.         var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil)
  284.         if isSelected {
  285.             switch selectedElement.element {
  286.             case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionColor
  287.             case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagColor
  288.             case .URL(_): attributes[NSForegroundColorAttributeName] = URLColor
  289.             case .None: ()
  290.             }
  291.         } else {
  292.             switch selectedElement.element {
  293.             case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionSelectedColor ?? mentionColor
  294.             case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagSelectedColor ?? hashtagColor
  295.             case .URL(_): attributes[NSForegroundColorAttributeName] = URLSelectedColor ?? URLColor
  296.             case .None: ()
  297.             }
  298.         }
  299.        
  300.         textStorage.addAttributes(attributes, range: selectedElement.range)
  301.        
  302.         setNeedsDisplay()
  303.     }
  304.    
  305.     private func elementAtLocation(location: CGPoint) -> (range: NSRange, element: ActiveElement)? {
  306.         guard textStorage.length > 0 else {
  307.             return nil
  308.         }
  309.        
  310.         let boundingRect = layoutManager.boundingRectForGlyphRange(NSRange(location: 0, length: textStorage.length), inTextContainer: textContainer)
  311.         guard boundingRect.contains(location) else {
  312.             return nil
  313.         }
  314.        
  315.         let index = layoutManager.glyphIndexForPoint(location, inTextContainer: textContainer)
  316.        
  317.         for element in activeElements.map({ $0.1 }).flatten() {
  318.             if index >= element.range.location && index <= element.range.location + element.range.length {
  319.                 return element
  320.             }
  321.         }
  322.        
  323.         return nil
  324.     }
  325.    
  326. }
  327.  
  328. extension ActiveLabel: UIGestureRecognizerDelegate {
  329.    
  330.     public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  331.         return true
  332.     }
  333.    
  334. }
Add Comment
Please, Sign In to add comment