swift中TextKit实现动态图文

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实现动态图文)