swift中TextKit實(shí)現(xiàn)動(dòng)態(tài)圖文

TextKit基礎(chǔ)知識(shí)可以去看看這篇文章,http://www.reibang.com/p/3f445d7f44d6
本次demo如下

  • 第一版


    ??
  • 加強(qiáng)版 可自動(dòng)識(shí)別連接 點(diǎn)擊連接跳轉(zhuǎn)

第二版

第一版 源碼豁辉,界面上放了一個(gè)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實(shí)現(xiàn)文字高亮
        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: "但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn)哄芜,這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知似枕,思考習(xí)慣易猫,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng)桶至,所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事歌懒,推遲滿足感跟压,抑制住欲望』等,他才能夠做到歼培。甚至是輕而易舉、自然而然的他就抵制住了誘惑茸塞,推遲了滿足感躲庄。但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn),這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知钾虐,思考習(xí)慣噪窘,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng),所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事效扫,推遲滿足感倔监,抑制住欲望』等直砂,他才能夠做到。甚至是輕而易舉浩习、自然而然的他就抵制住了誘惑静暂,推遲了滿足感。但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn)谱秽,這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知洽蛀,思考習(xí)慣,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng)疟赊,所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事郊供,推遲滿足感,抑制住欲望』等近哟,他才能夠做到驮审。甚至是輕而易舉、自然而然的他就抵制住了誘惑吉执,推遲了滿足感疯淫。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn),這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知鼠证,思考習(xí)慣峡竣,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng),所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事量九,推遲滿足感适掰,抑制住欲望』等,他才能夠做到荠列。甚至是輕而易舉类浪、自然而然的他就抵制住了誘惑,推遲了滿足感肌似。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) // 坐標(biāo)轉(zhuǎn)換
        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實(shí)現(xiàn)文字高亮
        
        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 但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn)费就,這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知,思考習(xí)慣川队,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng)力细,所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事,推遲滿足感固额,抑制住欲望』等眠蚂,他才能夠做到。甚至是輕而易舉斗躏、自然而然的他就抵制住了誘惑逝慧,推遲了滿足感。但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn),這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知笛臣,思考習(xí)慣云稚,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng),所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事沈堡,推遲滿足感静陈,抑制住欲望』等,他才能夠做到踱蛀。甚至是輕而易舉窿给、自然而然的他就抵制住了誘惑,推遲了滿足感率拒。但是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn)崩泡,這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知,思考習(xí)慣猬膨,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng)角撞,所以那些在沒有自制力的人 http://zuber.im 看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事,推遲滿足感勃痴,抑制住欲望』等谒所,他才能夠做到。甚至是輕而易舉沛申、自然而然的他就抵制住了誘惑劣领,推遲了滿足感。xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx但 http://beyondvincent.com/2013/11/12/2013-11-12-121-brief-analysis-text-kit/#1 是所謂的自控力只是人的一種「自然而然」的行為表現(xiàn)铁材,這種行為表現(xiàn)是因?yàn)橛兴麅?nèi)在的「對世界的認(rèn)知尖淘,思考習(xí)慣,思維邏輯」等內(nèi)部因素的驅(qū)動(dòng)著觉,所以那些在沒有自制力的人看來十分困難的『 堅(jiān)持投入的做有價(jià)值的事村生,推遲滿足感,抑制住欲望』等饼丘,他才能夠做到趁桃。甚至是輕而易舉、自然而然的他就抵制住了誘惑肄鸽,推遲了滿足感卫病。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()
    }
    
//    需要排除的區(qū)域
    private func _updateExclusionPaths() {
        var circleFrame = self.txtV.convertRect(midV.bounds, fromView: midV) // 坐標(biāo)轉(zhuǎn)換
        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放入數(shù)組
                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
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市典徘,隨后出現(xiàn)的幾起案子忽肛,更是在濱河造成了極大的恐慌,老刑警劉巖烂斋,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡汛骂,警方通過查閱死者的電腦和手機(jī)罕模,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帘瞭,“玉大人淑掌,你說我怎么就攤上這事〉睿” “怎么了抛腕?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長媒殉。 經(jīng)常有香客問我担敌,道長,這世上最難降的妖魔是什么廷蓉? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任全封,我火速辦了婚禮,結(jié)果婚禮上桃犬,老公的妹妹穿的比我還像新娘刹悴。我一直安慰自己,他們只是感情好攒暇,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布土匀。 她就那樣靜靜地躺著,像睡著了一般形用。 火紅的嫁衣襯著肌膚如雪就轧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天尾序,我揣著相機(jī)與錄音钓丰,去河邊找鬼。 笑死每币,一個(gè)胖子當(dāng)著我的面吹牛携丁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兰怠,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼梦鉴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了揭保?” 一聲冷哼從身側(cè)響起肥橙,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秸侣,沒想到半個(gè)月后存筏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宠互,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年椭坚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了予跌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡善茎,死狀恐怖券册,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垂涯,我是刑警寧澤烁焙,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站耕赘,受9級(jí)特大地震影響骄蝇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鞠苟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一乞榨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧当娱,春花似錦吃既、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冀惭,卻和暖如春震叙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背散休。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工媒楼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戚丸。 一個(gè)月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓划址,卻偏偏與公主長得像,于是被迫代替她去往敵國和親限府。 傳聞我的和親對象是個(gè)殘疾皇子夺颤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,318評論 25 707
  • 沒有什么不會(huì)老去 即使 每段青春 姍姍來遲 不過是想起那路過心上的句子 墳?zāi)挂膊槐厝绱搜?指環(huán)莫非是祭器 別把我...
    渺渺一沙閱讀 209評論 0 2
  • 好久沒在簡書上記錄自己生活中的點(diǎn)點(diǎn)滴滴了,可能是因?yàn)槊搯伟尚采住_@次主要當(dāng)做回憶吧世澜。這些天有所牽掛,也被牽掛署穗,或許孤獨(dú)...
    YuxinChao閱讀 226評論 0 0