TextKit基础知识可以去看看这篇文章,//www.greatytc.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
}