swift中TextKit实现动态图文

作者: smalldu | 来源:发表于2015-12-16 23:15 被阅读831次

    TextKit基础知识可以去看看这篇文章,http://www.jianshu.com/p/3f445d7f44d6
    本次demo如下

    • 第一版


      🌰
    • 加强版 可自动识别连接 点击连接跳转

    第二版

    第一版 源码,界面上放了一个textview

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var txtV:UITextView!
        var midV:UIView!
        
        var originalPos:CGPoint?
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            //属性字
    //        let attributedString = NSMutableAttributedString(attributedString: txtV.attributedText!)
    //        attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0,10))
    //        txtV.attributedText = attributedString
            //Text Storage实现文字高亮
            self.txtV.text = ""
            let frame = self.txtV.bounds
            let textStrage = NSTextStorage()
            let layoutManager = NSLayoutManager()
            textStrage.addLayoutManager(layoutManager)
            let containner = NSTextContainer(size: frame.size)
            layoutManager.addTextContainer(containner)
            
            txtV.textStorage.replaceCharactersInRange(NSMakeRange(0, 0), withString: "但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
            self._highlight()
            
            midV = UIView()
            midV.frame = CGRectMake(30, 30, 80, 80)
            midV.backgroundColor = UIColor.purpleColor()
            midV.layer.cornerRadius = 40
            midV.layer.masksToBounds = true
            midV.layer.shouldRasterize = true
            midV.layer.rasterizationScale = UIScreen.mainScreen().scale
            
            txtV.addSubview(midV)
            originalPos = midV.frame.origin
            let pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
            self.midV.addGestureRecognizer(pan)
            _updateExclusionPaths()
        }
        
        private func _highlight() {
            txtV.textStorage.beginEditing()
            
            // 属性描述字典
            let attributesDict = [NSForegroundColorAttributeName:UIColor.redColor()]
            
            txtV.textStorage.setAttributes(attributesDict, range: NSMakeRange(0, 5))
            
            txtV.textStorage.endEditing()
        }
        
        
        private func _updateExclusionPaths() {
            var circleFrame = self.txtV.convertRect(midV.bounds, fromView: midV) // 坐标转换
            circleFrame.origin.x = circleFrame.origin.x - txtV.textContainerInset.left
            circleFrame.origin.y = circleFrame.origin.y - txtV.textContainerInset.top
            let circlePath = UIBezierPath(roundedRect: circleFrame, cornerRadius: 40)
            txtV.textContainer.exclusionPaths = [circlePath]
        }
    
        var orp:CGPoint!
        func handlePan(gesture:UIPanGestureRecognizer){
            let p = gesture.locationInView(self.txtV)
            if gesture.state == .Began{
                orp = p
            }else if gesture.state == .Changed{
                midV.frame.origin.x = originalPos!.x+p.x-orp.x
                midV.frame.origin.y = originalPos!.y+p.y-orp.y
            }else if gesture.state == .Ended{
                originalPos = p
            }
            self._updateExclusionPaths()
        }
        
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }
    

    第二版源码

    import UIKit
    import SafariServices
    
    class ViewController: UIViewController {
    
        var txtV:UITextView!
        var midV:UIView!
        let textStrage = NSTextStorage()
        let layoutManager = NSLayoutManager()
        var containner:NSTextContainer!
        var originalPos:CGPoint?
        override func viewDidLoad() {
            super.viewDidLoad()
            //Text Storage实现文字高亮
            
            let frame = CGRectMake(0, 30, 300, 500)
            textStrage.addLayoutManager(layoutManager)
            containner = NSTextContainer(size: frame.size)
            layoutManager.addTextContainer(containner)
    
            txtV = UITextView(frame: frame, textContainer: containner)
            self.view.addSubview(txtV)
            self.txtV.text = ""
            txtV.editable = false
            let str = "https://zuber.im 但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。但是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人 http://zuber.im 看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx但 http://beyondvincent.com/2013/11/12/2013-11-12-121-brief-analysis-text-kit/#1 是所谓的自控力只是人的一种「自然而然」的行为表现,这种行为表现是因为有他内在的「对世界的认知,思考习惯,思维逻辑」等内部因素的驱动,所以那些在没有自制力的人看来十分困难的『 坚持投入的做有价值的事,推迟满足感,抑制住欲望』等,他才能够做到。甚至是轻而易举、自然而然的他就抵制住了诱惑,推迟了满足感。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx http://zuber.im"
            
    //        txtV.textStorage.replaceCharactersInRange(NSMakeRange(0, 0), withString: str)
            self.parseTextAndExtractActiveElements(str)
            
            textStrage.setAttributedString(self.addLinkAttribute(str))
            self._highlight()
            
            midV = UIView()
            midV.frame = CGRectMake(30, 30, 80, 80)
            midV.backgroundColor = UIColor.purpleColor()
            midV.layer.cornerRadius = 40
            midV.layer.masksToBounds = true
            midV.layer.shouldRasterize = true
            midV.layer.rasterizationScale = UIScreen.mainScreen().scale
            
            txtV.addSubview(midV)
            originalPos = midV.frame.origin
            let pan = UIPanGestureRecognizer(target: self, action: "handlePan:")
            self.midV.addGestureRecognizer(pan)
            _updateExclusionPaths()
            
            self.txtV.userInteractionEnabled = true
            let tap = UITapGestureRecognizer(target: self, action: "tapurl:")
            self.txtV.addGestureRecognizer(tap)
            
            print(self.reduceRightToURL(str))
            self.handleURLTap { (url) -> () in
                let safariViewController = SFSafariViewController(URL: url)
                self.presentViewController(safariViewController, animated: true, completion: nil)
            }
        }
        
        
        func handleURLTap(handler: (NSURL) -> ()) {
            urlTapHandler = handler
        }
        // MARK: - private properties
        private var urlTapHandler: ((NSURL) -> ())?
        
        private func _highlight() {
            txtV.textStorage.beginEditing()
            
            // 属性描述字典
            let attributesDict = [NSForegroundColorAttributeName:UIColor.redColor()]
            
            txtV.textStorage.setAttributes(attributesDict, range: NSMakeRange(0, 5))
            
            txtV.textStorage.endEditing()
        }
        
    //    需要排除的区域
        private func _updateExclusionPaths() {
            var circleFrame = self.txtV.convertRect(midV.bounds, fromView: midV) // 坐标转换
            circleFrame.origin.x = circleFrame.origin.x - txtV.textContainerInset.left
            circleFrame.origin.y = circleFrame.origin.y - txtV.textContainerInset.top
            let circlePath = UIBezierPath(roundedRect: circleFrame, cornerRadius: 40)
            txtV.textContainer.exclusionPaths = [circlePath]
        }
    
        var orp:CGPoint!
        func handlePan(gesture:UIPanGestureRecognizer){
            let p = gesture.locationInView(self.txtV)
            
            
            if gesture.state == .Began{
                orp = p
            }else if gesture.state == .Changed{
                midV.frame.origin.x = originalPos!.x+p.x-orp.x
                midV.frame.origin.y = originalPos!.y+p.y-orp.y
            }else if gesture.state == .Ended{
                originalPos = p
            }
            self._updateExclusionPaths()
        }
        private lazy var activeElements: [ZZType: [(range: NSRange, element: ZZElement)]] = [
            .URL: []
        ]
        
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        private var selectedElement: (range: NSRange, element: ZZElement )?
    }
    
    extension  ViewController{
    
        
        // MARK: - touch events
        func tapurl(gesture: UITapGestureRecognizer) {
            let location = gesture.locationInView(self.txtV)
            
            switch gesture.state {
            case .Began, .Changed:
                if let element = elementAtLocation(location) {
                    if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length {
    //                    updateAttributesWhenSelected(false)
                        selectedElement = element
    //                    updateAttributesWhenSelected(true)
                    }
                } else {
    //                updateAttributesWhenSelected(false)
                    selectedElement = nil
                }
            case .Cancelled, .Ended:
                if let element = elementAtLocation(location) {
                    if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length {
                        //                    updateAttributesWhenSelected(false)
                        selectedElement = element
                        //                    updateAttributesWhenSelected(true)
                    }
                    
                    switch selectedElement!.element {
                    case .URL(let url): urlTapHandler?(url)
                    case .None: ()
                    }
                    
                    let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC)))
                    dispatch_after(when, dispatch_get_main_queue()) {
                        //                self.updateAttributesWhenSelected(false)
                        self.selectedElement = nil
                    }
                } else {
                    //                updateAttributesWhenSelected(false)
                    selectedElement = nil
                }
           
            default: ()
            }
        }
        
        
        private func elementAtLocation(location: CGPoint) -> (range: NSRange, element: ZZElement )?{
            
            let boundingRect = layoutManager.boundingRectForGlyphRange(NSRange(location: 0, length: self.textStrage.length), inTextContainer: containner)
            guard boundingRect.contains(location) else {
                return nil
            }
            print(location)
            let index = layoutManager.glyphIndexForPoint(location, inTextContainer:containner)
            print(index)
            for element in activeElements.map({ $0.1 }).flatten() {
                print("element \(element.range.location)")
                if index >= element.range.location && index <= element.range.location + element.range.length {
                    return element
                }
            }
            
            return nil
        }
        
    
        
        /// add link attribute
        private func addLinkAttribute(str: String) ->NSMutableAttributedString{
            let mutAttrString = NSMutableAttributedString(string: str)
            var range = NSRange(location: 0, length: 0)
            var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range)
            
            for (type, elements) in activeElements {
                
                switch type {
                case .URL: attributes[NSForegroundColorAttributeName] = UIColor.blueColor()
                case .None: ()
                }
                for element in elements {
                    mutAttrString.setAttributes(attributes, range: element.range)
                }
            }
            
            return mutAttrString
        }
        
        private func parseTextAndExtractActiveElements(attrString: String) {
            let textString = attrString as NSString
            for word in textString.componentsSeparatedByString(" ") {
                let element = activeElement(word)
                switch element {
                case .URL(let url):
                    //将匹配的连接的range放入数组
                    activeElements[.URL]?.append((textString.rangeOfString(url.absoluteString), element))
                default: ()
                }
            }
        }
        //MARK: - 判断是否为URL
        private func reduceRightToURL(str: String) -> NSURL? {
            if let regex = try? NSRegularExpression(pattern: "(?i)https?://(?:www\\.)?\\S+(?:/|\\b)", options: [.CaseInsensitive]) {
                let nsStr = str as NSString
                let results = regex.matchesInString(str, options: [], range: NSRange(location: 0, length: nsStr.length))
                if let result = results.map({ nsStr.substringWithRange($0.range) }).first, url = NSURL(string: result) {
                    return url
                }
            }
            return nil
        }
        
        //MARK: -返回匹配元素
        func activeElement(word: String) -> ZZElement {
            if let url = reduceRightToURL(word) {
                return .URL(url)
            }
            
            if word.characters.count < 2 {
                return .None
            }
            return .None
        }
    }
    
    enum ZZType {
        case URL
        case None
    }
    
    enum ZZElement{
        case URL(NSURL)
        case None
    }
    

    相关文章

      网友评论

      本文标题:swift中TextKit实现动态图文

      本文链接:https://www.haomeiwen.com/subject/haqphttx.html