? ? ? ?圖文混排版可以說(shuō)在很多地方都常用茂契,但真正想實(shí)現(xiàn)一些高級(jí)的效果還是要耗費(fèi)不少的精力去系統(tǒng)的學(xué)習(xí)的。圖文混排實(shí)現(xiàn)的方式一般有兩種方式:基于Html5和蘋(píng)果提供的框架(TextKit或者CoreText)慨绳。账嚎。
? ? ? ?先簡(jiǎn)單說(shuō)下第一種方式,一般是通過(guò)OC和JS相互調(diào)用實(shí)現(xiàn)的儡蔓,其中OC和JS相互調(diào)用可以有三種實(shí)現(xiàn)方式分別是UIWebVIew郭蕉,WKWebView,以及JavaScriptCore三種方式喂江。UIWebView雖然比較穩(wěn)定但是占用的資源比較多召锈,WKWebView占用資源相對(duì)于UIWebView來(lái)說(shuō)很小,但是目前來(lái)看依然沒(méi)有得到較為廣泛的應(yīng)用获询,原因在于WKWebView在穩(wěn)定性方面依然存在一些細(xì)小的問(wèn)題涨岁。三種簡(jiǎn)單的調(diào)用方式直接提供給源碼給大家看一下https://github.com/ZhengYaWei1992/1.JSAndOC,后續(xù)的話會(huì)在之后的簡(jiǎn)書(shū)文章中更新吉嚣。Tips:更新介紹的內(nèi)容含有JS和OC相互調(diào)用傳參問(wèn)題梢薪、第三方模板框架使用問(wèn)題、以及類似網(wǎng)易新聞這種圖文混排界面是如何實(shí)現(xiàn)的尝哆。之前有些過(guò)一個(gè)小小的實(shí)現(xiàn)類似網(wǎng)易新聞詳情界面實(shí)現(xiàn)的小Demo秉撇,之后一并分享給大家。這和不是今天的最主要話題秋泄,扯的有點(diǎn)多了琐馆。
? ? ? ? 接下來(lái)先說(shuō)下第二種實(shí)現(xiàn)方式,TextKit或者CoreText恒序。注意是或者,其實(shí)之前有段時(shí)間非常強(qiáng)烈的認(rèn)真學(xué)習(xí)過(guò)一寫(xiě)和CoreText相關(guān)的東西滋饲,但是學(xué)者學(xué)著真是無(wú)力吐槽了屠缭。發(fā)現(xiàn)實(shí)現(xiàn)各簡(jiǎn)單的圖文混排效果實(shí)在是太繁瑣了勿她,后來(lái)果斷放棄阵翎。不過(guò)現(xiàn)在發(fā)現(xiàn)郭卫,以前放棄學(xué)習(xí)CoreText確實(shí)是明智之舉贰军,因?yàn)閕OS7之后有個(gè)比它更好使用词疼,更容易實(shí)現(xiàn)圖文混排效果的框架TextKit(后來(lái)才知道這個(gè)框架贰盗,之前走了不少的彎路)。
? ? ? ?先來(lái)展示下陋率,之前自己封裝的一個(gè)圖文混排效果圖瓦糟∑姓悖可以自動(dòng)識(shí)別表情芍耘,URL和昵稱,當(dāng)然還可以識(shí)別更多(最主要的是正則表達(dá)式的應(yīng)用)斋竞。使用起來(lái)也十分簡(jiǎn)單坝初,直接繼承與ZWLabel這個(gè)類就可以鳄袍。暫時(shí)不放代碼拗小,后續(xù)再繼續(xù)更新哀九,先看看效果。
? ? ? ? 本文先從實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自定義識(shí)別網(wǎng)址的圖文混排開(kāi)始呼胚。初體驗(yàn)之效果圖:
? ? ? ?學(xué)習(xí)TextKit的第一步:是需要認(rèn)識(shí)里面的三個(gè)類NSTextStorage、NSLayoutManager呼盆、NSTextContainer分別的作用和職責(zé)。三者的職責(zé):NSTextStorage負(fù)責(zé)文本存儲(chǔ)常遂,NSLayoutManager負(fù)責(zé)文本字形布局克胳,NSTextContainer設(shè)定文本繪制的范圍(一般開(kāi)發(fā)很少用漠另,定義一個(gè)矩形區(qū)域笆搓,也可以定義一個(gè)排除路徑)满败。第二步:要認(rèn)清實(shí)現(xiàn)自定義識(shí)別網(wǎng)址的Label的本質(zhì)是:使用textKit接替label的底層實(shí)現(xiàn)算墨。第三步:實(shí)現(xiàn)自定義識(shí)別網(wǎng)址URL的步驟:1.使用textKit接替label的底層實(shí)現(xiàn)? ---繪制textStorage的文本內(nèi)容汁雷。2.使用正則表達(dá)式過(guò)濾URL侠讯。3.解決交互問(wèn)題厢漩。
? ? ? ?開(kāi)始上主菜代碼了,先來(lái)個(gè)簡(jiǎn)單的Swift代碼。具體Demo鏈接宵膨,請(qǐng)?jiān)谖恼履┪膊榭础?/p>
1.ZWLabel.swift中
```
import UIKit
class ZWLabel: UILabel { ? ?
? ? ? ? lazy var textStorage = NSTextStorage() ??
? ? ? ? lazy var layoutManager = NSLayoutManager() ??
? ? ? ? ?lazy var textContainer = NSTextContainer()? ??
? ? ? ? override var text: String?{? ? ? ?
? ? ? ? ? ? ?didSet{ ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? prepareTextContent() ? ? ? ?
? ? ? ? ? ? ? ? }? ?
? ? ? ? ? } ? ? ??
? ? ? ?override init(frame: CGRect) {? ? ? ??
? ? ? ? ? ? ? super.init(frame: frame)? ? ? ?
? ? ? ? ? ? ? backgroundColor = UIColor.orange? ? ? ??
? ? ? ? ? ? ? ?prepareTextSystem()??
? ? ? ? }? ? ? ?
? ? ? ?required init?(coder aDecoder: NSCoder) { ? ??
? ? ? ? ? ? ? ?super.init(coder: aDecoder)? ? ??
? ? ? ? ? ? ? backgroundColor = UIColor.orange? ? ? ??
? ? ? ? ? ? ? prepareTextSystem()??
? ? ? ?} ? ? ? ?
? ? override func drawText(in rect: CGRect){? ? ? ?
? ? ? ? ? ? ? ?//消除父類的繪制狐树,這里我們自己繪制? ? ? // super.drawText(in: rect)? ?
? ? ? ? ? ? ? let range = NSRange(location: 0, length: textStorage.length) ? ? ? ?
? ? ? ? ? ? ?layoutManager.drawBackground(forGlyphRange: range, at:? CGPoint())? ??
? ? ? ? ? ? ? //繪制Glyph字形? ? CGPoint()表示從原點(diǎn)開(kāi)始繪制 ? ??
? ? ? ? ? ? ?layoutManager.drawGlyphs(forGlyphRange: range, at: CGPoint())? ? ? ? ?
? ? ?} ? ??
? ? ? override func layoutSubviews() {? ? ? ?
? ? ? ? ? ? ? ? ? ?super.layoutSubviews()? ? ? ??
? ? ? ? ? ? ? ? //指定文本的繪制區(qū)域? ? ? ??
? ? ? ? ? ? ? ?textContainer.size = bounds.size ??
? ? ?}? ? ?
? ? override func touchesBegan(_ touches: Set, with event: UIEvent?) {
? ? ? ? ? ? //1.獲取用戶點(diǎn)擊的位置
? ? ? ? ? ? ? //first?相當(dāng)于OC的anyObject
? ? ? ? ? ?guard let location = touches.first?.location(in: self)else{
? ? ? ? ? ? ? ? ? ?return
? ? ? ? ? }
? ? ? ? ? ?print("點(diǎn)我了\(location)")
? ? ? ? //2.記錄當(dāng)前點(diǎn)中字符的索引
? ? ? ? ?let idx = layoutManager.glyphIndex(for: location, in: textContainer)
? ? ? ? ? print("點(diǎn)我了\(idx)")
? ? ? ? ? ?//3.判斷idx是否在url的range范圍內(nèi),如果在就高亮
? ? ? for r in urlRanges ?? []{
? ? ? ? ? ? //NSRanege跳入頭文件可以看見(jiàn)次方法 NSLocationInRange
? ? ? ? ? ? if NSLocationInRange(idx, r){
? ? ? ? ? ? ? print("需要高亮")
? ? ? ? ? ? //修改文本的字體屬性
? ? ? ? ? ? textStorage.addAttributes([NSForegroundColorAttributeName:UIColor.blue], range: r)
? ? ? ? ? ? ?//如果需要重新繪制野哭,需要調(diào)用setNeedsDisplay方法
? ? ? ? ? ?setNeedsDisplay()
? ? ? ? ? ?}else{
? ? ? ? ? ? ? ? ? ? print("沒(méi)點(diǎn)到")
? ? ? ? ? ?}
? ? ? ? ?}
? ?}
}
// MARK: - 設(shè)置textKit核心對(duì)象
private extension ZWLabel{
? ? ? ? ?///準(zhǔn)備文本系統(tǒng)
? ? ? ? ?func prepareTextSystem(){
? ? ? ? ? ? ? ? ? //0.開(kāi)啟用戶交互
? ? ? ? ? ? ? ? ? ? ?isUserInteractionEnabled = true
? ? ? ? ? ? ? ? ?//1.準(zhǔn)備文本內(nèi)容
? ? ? ? ? ? ? ? ?prepareTextContent()
? ? ? ? ? ? ? ? ? //2.設(shè)置對(duì)象的關(guān)系? ---一般都是固定的寫(xiě)法
? ? ? ? ? ? ? ? textStorage.addLayoutManager(layoutManager)
? ? ? ? ? ? ? ? ? ? layoutManager.addTextContainer(textContainer)
? ? ? ? ? ? ?}
? ? ? ? ? ?/// 準(zhǔn)備文本內(nèi)容拨黔,接管label內(nèi)容
? ? ? ? ?func prepareTextContent(){
? ? ? ? ? ? ? ? if let attributedText = attributedText{
? ? ? ? ? ? ? ? textStorage.setAttributedString(attributedText)
? ? ? ? ?}else if let text = text{
? ? ? ? ? ? ? ? textStorage.setAttributedString(NSAttributedString(string: text))
? ? ? ? }else{
? ? ? ? ? ? ? textStorage.setAttributedString(NSAttributedString(string: ""))
? ? ? ? ?}
? ? ? ? ? ?// print(urlRanges)
? ? ? ?//遍歷范圍數(shù)組篱蝇,設(shè)置URL文字的屬性
? ? ? ? ?for r in urlRanges ?? []{
? ? ? ? ? textStorage.addAttributes([NSForegroundColorAttributeName:UIColor.red,NSBackgroundColorAttributeName:UIColor.init(white: 0.9, alpha: 1.0)], range: r)
? ? ? ? ? ? }
? ? ?}
}
// MARK: - 正則表達(dá)式獲取url范圍
extension ZWLabel{
? ? ? ? ? ? ?///返回textStorage中的URL range數(shù)組
? ? ? ? ? ? ?var urlRanges: [NSRange]?{
? ? ? ? ? ? ? ? ? ? ? ?let pattern = "[a-zA-Z]*://[a-zA-Z0-9/\\.]*"
? ? ? ? ? ? ? ? ? ? ?guard let regx = try? NSRegularExpression(pattern: pattern, options: [])else{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? return nil
? ? ? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? ? //2.多重匹配零截,因?yàn)橐粋€(gè)字符串中可能包含多個(gè)url
? ? ? ? ? ? ? ? ? let matches = regx.matches(in: textStorage.string, options: [], range:NSRange(location: 0, length: textStorage.length))
? ? ? ? ? ? ? ? //3.遍歷數(shù)組涧衙,生成range數(shù)組
? ? ? ? ? ? ? ?var ranges = [NSRange]()
? ? ? ? ? ? ? ?for m in matches{
? ? ? ? ? ? ? ? ? ? ? ? ranges.append(m.rangeAt(0))
? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? ? ? return ranges
? ? ?}
}
```
2.外部調(diào)用形式
```
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: ZWLabel!
? ? ? ? ?override func viewDidLoad() {
? ? ? ? ? ? ? ? ? super.viewDidLoad()
? ? ? ? ? ? ? ? ? ?label.text = "請(qǐng)點(diǎn)擊http://www.baidu.com"
? ? ? ? ? ?}
}
```
當(dāng)然以上代碼只是簡(jiǎn)單的認(rèn)識(shí)一下TextKit這個(gè)框架,實(shí)際上想真正的完成一個(gè)自動(dòng)識(shí)別URL稚虎,添加表情圖片的圖文混排label還是有很多事情要做的蠢终,比如還要考慮行高蜕径、字體間距、點(diǎn)擊時(shí)顯示的效果等因素梦染。實(shí)際上添加上表情就是相對(duì)比較簡(jiǎn)單的事情了帕识,主要是通過(guò)屬性文本肮疗,代碼如下(具體實(shí)現(xiàn)自行添加):
```
//0.圖片附件
let attachment = NSTextAttachment()
attachment.image = #imageLiteral(resourceName: "d_aini.png")
let height = label.font.lineHeight
attachment.bounds = CGRect(x: 0, y: -4, width: height, height: height)
//1.屬性文本
let imageStr =? NSAttributedString(attachment:attachment )
//2.可變的圖文字符串 ----注意:這里可變的要用NSMutableAttributedString伪货,用var不起作用
let attStrM = NSMutableAttributedString(string: "我")
attStrM.append(imageStr)
attStrM.append(NSAttributedString(string: "99!"))
//3.設(shè)置label
label.attributedText = attStrM
```