From b20c78adef1e4995dc118dc556cdf48a7774f9cf Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Tue, 15 Mar 2016 00:19:34 +0100 Subject: [PATCH 001/116] Avoid multiple calls to updateTextStorage by batching customizations --- ActiveLabel/ActiveLabel.swift | 17 +++++++++++++++-- ActiveLabelDemo/ViewController.swift | 26 ++++++++++++++------------ README.md | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2832f6a2..91682c39 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -77,13 +77,13 @@ public protocol ActiveLabelDelegate: class { // MARK: - init functions override public init(frame: CGRect) { super.init(frame: frame) - + _customizing = false setupLabel() } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - + _customizing = false setupLabel() } @@ -96,6 +96,16 @@ public protocol ActiveLabelDelegate: class { layoutManager.drawBackgroundForGlyphRange(range, atPoint: newOrigin) layoutManager.drawGlyphsForGlyphRange(range, atPoint: newOrigin) } + + + // MARK: - customzation + public func customize(block: (label: ActiveLabel) -> ()) -> ActiveLabel{ + _customizing = true + block(label: self) + _customizing = false + updateTextStorage() + return self + } // MARK: - touch events func onTouch(touch: UITouch) -> Bool { @@ -141,6 +151,8 @@ public protocol ActiveLabelDelegate: class { } // MARK: - private properties + private var _customizing: Bool = true + private var mentionTapHandler: ((String) -> ())? private var hashtagTapHandler: ((String) -> ())? private var urlTapHandler: ((NSURL) -> ())? @@ -165,6 +177,7 @@ public protocol ActiveLabelDelegate: class { } private func updateTextStorage(parseText parseText: Bool = true) { + if _customizing { return } // clean up previous active elements guard let attributedText = attributedText where attributedText.length > 0 else { diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index ea6acc24..12098cff 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -16,18 +16,20 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like this one: http://optonaut.co." - label.numberOfLines = 0 - label.lineSpacing = 4 - - label.textColor = UIColor(red: 102.0/255, green: 117.0/255, blue: 127.0/255, alpha: 1) - label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) - label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) - label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) - - label.handleMentionTap { self.alert("Mention", message: $0) } - label.handleHashtagTap { self.alert("Hashtag", message: $0) } - label.handleURLTap { self.alert("URL", message: $0.description) } + label.customize { label in + label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like this one: http://optonaut.co." + label.numberOfLines = 0 + label.lineSpacing = 4 + + label.textColor = UIColor(red: 102.0/255, green: 117.0/255, blue: 127.0/255, alpha: 1) + label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) + label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) + label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) + + label.handleMentionTap { self.alert("Mention", message: $0) } + label.handleHashtagTap { self.alert("Hashtag", message: $0) } + label.handleURLTap { self.alert("URL", message: $0.absoluteString) } + } label.frame = CGRect(x: 20, y: 40, width: view.frame.width - 40, height: 300) view.addSubview(label) diff --git a/README.md b/README.md index cc55e2af..e6de40af 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,31 @@ label.handleHashtagTap { hashtag in } ``` +## Batched customization + +When using ActiveLabel, it is recommended to use the `customize(block:)` method to customize it. The reason is that ActiveLabel is reacting to each property that you set. So if you set 3 properties, the textContainer is refreshed 3 times. + +When using `customize(block:)`, you can group all the customizations on the label, that way ActiveLabel is only going to refresh the textContainer once. + +Example: + +```swift + + label.customize { label in + label.text = "This is a post with #multiple #hashtags and a @userhandle." + label.textColor = UIColor(red: 102.0/255, green: 117.0/255, blue: 127.0/255, alpha: 1) + label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) + label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) + label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) + label.handleMentionTap { self.alert("Mention", message: $0) } + label.handleHashtagTap { self.alert("Hashtag", message: $0) } + label.handleURLTap { self.alert("URL", message: $0.absoluteString) } + } + + +``` + + ## API ##### `mentionColor: UIColor = .blueColor()` From 086096b3ed28e05632e258486ed28003e4dec7dc Mon Sep 17 00:00:00 2001 From: Keilan Jackson Date: Tue, 15 Mar 2016 18:54:36 -0600 Subject: [PATCH 002/116] Unset selected element when parsing text --- ActiveLabel/ActiveLabel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2832f6a2..07a99c34 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -174,6 +174,7 @@ public protocol ActiveLabelDelegate: class { let mutAttrString = addLineBreak(attributedText) if parseText { + selectedElement = nil for (type, _) in activeElements { activeElements[type]?.removeAll() } From de018706afed1198ad3cdcad14f02c6462e87715 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Mon, 21 Mar 2016 22:42:55 +0100 Subject: [PATCH 003/116] Podspec bump --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index e87e741c..5ec89de1 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.4.2' + s.version = '0.5.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From 05557c8eea4750486daee73888afd196d4bc4991 Mon Sep 17 00:00:00 2001 From: lastMove Date: Sun, 27 Mar 2016 07:39:51 +0200 Subject: [PATCH 004/116] Add support for accent/diacritics (fix #58) --- ActiveLabel/RegexParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 254a8d22..3796c323 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -14,8 +14,8 @@ struct RegexParser { "((https?://|www.|pic.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" - static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[a-z0-9_]*", options: [.CaseInsensitive]) - static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[a-z0-9_]*", options: [.CaseInsensitive]) + static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[\\p{L}0-9_]*", options: [.CaseInsensitive]) + static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_]*", options: [.CaseInsensitive]); static let urlDetector = try? NSRegularExpression(pattern: urlPattern, options: [.CaseInsensitive]) static func getMentions(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { From b591413d1c0981baa2846a72181c8f20f2a60bd0 Mon Sep 17 00:00:00 2001 From: Keilan Jackson Date: Sat, 2 Apr 2016 11:29:30 -0600 Subject: [PATCH 005/116] Add mention and hashtag filtering --- ActiveLabel/ActiveLabel.swift | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 450ba2a7..5a54bb7e 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -39,7 +39,7 @@ public protocol ActiveLabelDelegate: class { @IBInspectable public var lineSpacing: Float? { didSet { updateTextStorage(parseText: false) } } - + // MARK: - public methods public func handleMentionTap(handler: (String) -> ()) { mentionTapHandler = handler @@ -52,7 +52,17 @@ public protocol ActiveLabelDelegate: class { public func handleURLTap(handler: (NSURL) -> ()) { urlTapHandler = handler } - + + public func filterMention(predicate: (String) -> Bool) { + mentionFilterPredicate = predicate + updateTextStorage() + } + + public func filterHashtag(predicate: (String) -> Bool) { + hashtagFilterPredicate = predicate + updateTextStorage() + } + // MARK: - override UILabel properties override public var text: String? { didSet { updateTextStorage() } @@ -156,6 +166,9 @@ public protocol ActiveLabelDelegate: class { private var mentionTapHandler: ((String) -> ())? private var hashtagTapHandler: ((String) -> ())? private var urlTapHandler: ((NSURL) -> ())? + + private var mentionFilterPredicate: ((String) -> Bool) = { _ in return false } + private var hashtagFilterPredicate: ((String) -> Bool) = { _ in return false } private var selectedElement: (range: NSRange, element: ActiveElement)? private var heightCorrection: CGFloat = 0 @@ -243,11 +256,23 @@ public protocol ActiveLabelDelegate: class { activeElements[.URL]?.appendContentsOf(urlElements) //HASHTAGS - let hashtagElements = ActiveBuilder.createHashtagElements(fromText: textString, range: textRange) + let hashtagElements = ActiveBuilder.createHashtagElements(fromText: textString, range: textRange).filter { (_, element: ActiveElement) in + + switch element { + case .Hashtag(let tag): return hashtagFilterPredicate(tag) + default: return true + } + } activeElements[.Hashtag]?.appendContentsOf(hashtagElements) //MENTIONS - let mentionElements = ActiveBuilder.createMentionElements(fromText: textString, range: textRange) + let mentionElements = ActiveBuilder.createMentionElements(fromText: textString, range: textRange).filter { (_, element: ActiveElement) in + + switch element { + case .Mention(let mention): return mentionFilterPredicate(mention) + default: return true + } + } activeElements[.Mention]?.appendContentsOf(mentionElements) } From 3a39f38e20ee8b3c886e3fd04d25fd8f49292357 Mon Sep 17 00:00:00 2001 From: Keilan Jackson Date: Sat, 2 Apr 2016 12:39:56 -0600 Subject: [PATCH 006/116] Update readme for hashtag and mention filter --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e6de40af..928e2141 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,18 @@ label.handleHashtagTap { hashtag in print("\(hashtag) tapped") } label.handleURLTap { url in UIApplication.sharedApplication().openURL(url) } ``` +##### `filterHashtag: (String) -> Bool` + +```swift +label.filterHashtag { hashtag in validHashtags.contains(hashtag) } +``` + +##### `filterMention: (String) -> Bool` + +```swift +label.filterMention { mention in validMentions.contains(mention) } +``` + ## Install (iOS 8+) ### Carthage From d94c0391cb0b981fce84565a0c35294e23b89259 Mon Sep 17 00:00:00 2001 From: Keilan Jackson Date: Mon, 4 Apr 2016 18:44:47 -0600 Subject: [PATCH 007/116] Filter elements in same pass as parsing, add test for filtering --- ActiveLabel/ActiveLabel.swift | 26 +++++++------------------- ActiveLabel/ActiveType.swift | 20 ++++++++++++++------ ActiveLabelTests/ActiveTypeTests.swift | 11 +++++++++++ 3 files changed, 32 insertions(+), 25 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 5a54bb7e..2d7e97b4 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -167,9 +167,9 @@ public protocol ActiveLabelDelegate: class { private var hashtagTapHandler: ((String) -> ())? private var urlTapHandler: ((NSURL) -> ())? - private var mentionFilterPredicate: ((String) -> Bool) = { _ in return false } - private var hashtagFilterPredicate: ((String) -> Bool) = { _ in return false } - + private var mentionFilterPredicate: ((String) -> Bool)? + private var hashtagFilterPredicate: ((String) -> Bool)? + private var selectedElement: (range: NSRange, element: ActiveElement)? private var heightCorrection: CGFloat = 0 private lazy var textStorage = NSTextStorage() @@ -254,25 +254,13 @@ public protocol ActiveLabelDelegate: class { //URLS let urlElements = ActiveBuilder.createURLElements(fromText: textString, range: textRange) activeElements[.URL]?.appendContentsOf(urlElements) - - //HASHTAGS - let hashtagElements = ActiveBuilder.createHashtagElements(fromText: textString, range: textRange).filter { (_, element: ActiveElement) in - switch element { - case .Hashtag(let tag): return hashtagFilterPredicate(tag) - default: return true - } - } + //HASHTAGS + let hashtagElements = ActiveBuilder.createHashtagElements(fromText: textString, range: textRange, filterPredicate: hashtagFilterPredicate) activeElements[.Hashtag]?.appendContentsOf(hashtagElements) - - //MENTIONS - let mentionElements = ActiveBuilder.createMentionElements(fromText: textString, range: textRange).filter { (_, element: ActiveElement) in - switch element { - case .Mention(let mention): return mentionFilterPredicate(mention) - default: return true - } - } + //MENTIONS + let mentionElements = ActiveBuilder.createMentionElements(fromText: textString, range: textRange, filterPredicate: mentionFilterPredicate) activeElements[.Mention]?.appendContentsOf(mentionElements) } diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index d43b64b2..11b3fbf7 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -22,9 +22,11 @@ public enum ActiveType { case None } +typealias ActiveFilterPredicate = (String -> Bool) + struct ActiveBuilder { - static func createMentionElements(fromText text: String, range: NSRange) -> [(range: NSRange, element: ActiveElement)] { + static func createMentionElements(fromText text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [(range: NSRange, element: ActiveElement)] { let mentions = RegexParser.getMentions(fromText: text, range: range) let nsstring = text as NSString var elements: [(range: NSRange, element: ActiveElement)] = [] @@ -35,13 +37,16 @@ struct ActiveBuilder { if word.hasPrefix("@") { word.removeAtIndex(word.startIndex) } - let element = ActiveElement.Mention(word) - elements.append((mention.range, element)) + + if filterPredicate?(word) ?? true { + let element = ActiveElement.Mention(word) + elements.append((mention.range, element)) + } } return elements } - static func createHashtagElements(fromText text: String, range: NSRange) -> [(range: NSRange, element: ActiveElement)] { + static func createHashtagElements(fromText text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [(range: NSRange, element: ActiveElement)] { let hashtags = RegexParser.getHashtags(fromText: text, range: range) let nsstring = text as NSString var elements: [(range: NSRange, element: ActiveElement)] = [] @@ -52,8 +57,11 @@ struct ActiveBuilder { if word.hasPrefix("#") { word.removeAtIndex(word.startIndex) } - let element = ActiveElement.Hashtag(word) - elements.append((hashtag.range, element)) + + if filterPredicate?(word) ?? true { + let element = ActiveElement.Hashtag(word) + elements.append((hashtag.range, element)) + } } return elements } diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index e70b13c0..01f83708 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -205,5 +205,16 @@ class ActiveTypeTests: XCTestCase { label.text = "google.com" XCTAssertEqual(activeElements.count, 0) } + + func testFiltering() { + label.text = "@user #tag" + XCTAssertEqual(activeElements.count, 2) + + label.filterMention { $0 != "user" } + XCTAssertEqual(activeElements.count, 1) + + label.filterHashtag { $0 != "tag" } + XCTAssertEqual(activeElements.count, 0) + } } From f60bfb7d8cdc569534c818d17676d0aaf8e35103 Mon Sep 17 00:00:00 2001 From: Rob Hudson Date: Mon, 18 Apr 2016 12:20:53 -0400 Subject: [PATCH 008/116] Selected color not applied appropriately --- ActiveLabel/ActiveLabel.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2d7e97b4..5cdf7178 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -293,16 +293,16 @@ public protocol ActiveLabelDelegate: class { var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil) if isSelected { switch selectedElement.element { - case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionColor - case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagColor - case .URL(_): attributes[NSForegroundColorAttributeName] = URLColor + case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionSelectedColor ?? mentionColor + case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagSelectedColor ?? hashtagColor + case .URL(_): attributes[NSForegroundColorAttributeName] = URLSelectedColor ?? URLColor case .None: () } } else { switch selectedElement.element { - case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionSelectedColor ?? mentionColor - case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagSelectedColor ?? hashtagColor - case .URL(_): attributes[NSForegroundColorAttributeName] = URLSelectedColor ?? URLColor + case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionColor + case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagColor + case .URL(_): attributes[NSForegroundColorAttributeName] = URLColor case .None: () } } From a971ba4b6482c25e5e7fff1aae1392e00699f06c Mon Sep 17 00:00:00 2001 From: Prokhor Kharchenko Date: Thu, 21 Apr 2016 16:15:10 +0300 Subject: [PATCH 009/116] fix for issue https://github.com/optonaut/ActiveLabel.swift/issues/64 --- ActiveLabel/RegexParser.swift | 2 +- ActiveLabelTests/ActiveTypeTests.swift | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 3796c323..52b14d95 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -11,7 +11,7 @@ import Foundation struct RegexParser { static let urlPattern = "(^|[\\s.:;?\\-\\]<\\(])" + - "((https?://|www.|pic.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + + "((https?://|www\\.|pic\\.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[\\p{L}0-9_]*", options: [.CaseInsensitive]) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 01f83708..508cf743 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -217,4 +217,15 @@ class ActiveTypeTests: XCTestCase { XCTAssertEqual(activeElements.count, 0) } + // test for issue https://github.com/optonaut/ActiveLabel.swift/issues/64 + func testIssue64pic() { + label.text = "picfoo" + XCTAssertEqual(activeElements.count, 0) + } + + // test for issue https://github.com/optonaut/ActiveLabel.swift/issues/64 + func testIssue64www() { + label.text = "wwwbar" + XCTAssertEqual(activeElements.count, 0) + } } From b2399610e2770c73329fb3cc07a0aa47944cad9a Mon Sep 17 00:00:00 2001 From: Econa77 Date: Fri, 22 Apr 2016 02:19:19 +0900 Subject: [PATCH 010/116] Add the operation during move --- ActiveLabel/ActiveLabel.swift | 6 ++++++ ActiveLabelDemo/ViewController.swift | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2d7e97b4..6295744f 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -342,6 +342,12 @@ public protocol ActiveLabelDelegate: class { if onTouch(touch) { return } super.touchesBegan(touches, withEvent: event) } + + public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { + guard let touch = touches.first else { return } + if onTouch(touch) { return } + super.touchesMoved(touches, withEvent: event) + } public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { guard let touch = touches?.first else { return } diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index 12098cff..c999000d 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -25,7 +25,8 @@ class ViewController: UIViewController { label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) - + label.URLSelectedColor = UIColor(red: 82.0/255, green: 190.0/255, blue: 41.0/255, alpha: 1) + label.handleMentionTap { self.alert("Mention", message: $0) } label.handleHashtagTap { self.alert("Hashtag", message: $0) } label.handleURLTap { self.alert("URL", message: $0.absoluteString) } From c2c6497c23fbe61f1384f4c4ad72889b28257898 Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 24 Apr 2016 17:31:54 +0900 Subject: [PATCH 011/116] Call updateAttributesWhenSelected on Cancelled --- ActiveLabel/ActiveLabel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2d7e97b4..f5ee8516 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -152,6 +152,7 @@ public protocol ActiveLabelDelegate: class { } avoidSuperCall = true case .Cancelled: + updateAttributesWhenSelected(false) selectedElement = nil case .Stationary: break From 1504cc75d3e3d4601aea6ecc055fc3ae87855d0b Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 24 Apr 2016 19:44:50 +0900 Subject: [PATCH 012/116] Fix linespacing setting. @IBInspectable is not support Float?. didSet is not called because they are not executed in runtime. --- ActiveLabel/ActiveLabel.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2d7e97b4..0e4502d1 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -36,7 +36,7 @@ public protocol ActiveLabelDelegate: class { @IBInspectable public var URLSelectedColor: UIColor? { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var lineSpacing: Float? { + @IBInspectable public var lineSpacing: Float = 0 { didSet { updateTextStorage(parseText: false) } } @@ -275,9 +275,7 @@ public protocol ActiveLabelDelegate: class { let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping paragraphStyle.alignment = textAlignment - if let lineSpacing = lineSpacing { - paragraphStyle.lineSpacing = CGFloat(lineSpacing) - } + paragraphStyle.lineSpacing = CGFloat(lineSpacing) attributes[NSParagraphStyleAttributeName] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) From f2ed0a40f285c1555057cca974153b2beb22c799 Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 24 Apr 2016 22:16:03 +0900 Subject: [PATCH 013/116] Add support numberOfLines and lineBreakMode --- ActiveLabel/ActiveLabel.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 2d7e97b4..082255c4 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -83,6 +83,14 @@ public protocol ActiveLabelDelegate: class { override public var textAlignment: NSTextAlignment { didSet { updateTextStorage(parseText: false)} } + + public override var numberOfLines: Int { + didSet { textContainer.maximumNumberOfLines = numberOfLines } + } + + public override var lineBreakMode: NSLineBreakMode { + didSet { textContainer.lineBreakMode = lineBreakMode } + } // MARK: - init functions override public init(frame: CGRect) { @@ -186,6 +194,8 @@ public protocol ActiveLabelDelegate: class { textStorage.addLayoutManager(layoutManager) layoutManager.addTextContainer(textContainer) textContainer.lineFragmentPadding = 0 + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines userInteractionEnabled = true } From 9ba320a2c8508fc15d91ffb9d2ea990243db495c Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 24 Apr 2016 04:00:02 +0900 Subject: [PATCH 014/116] Display text on uncustomized label --- ActiveLabel/ActiveLabel.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 228d4fcd..80c98d85 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -104,6 +104,11 @@ public protocol ActiveLabelDelegate: class { _customizing = false setupLabel() } + + public override func awakeFromNib() { + super.awakeFromNib() + updateTextStorage() + } public override func drawTextInRect(rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) From c9f9bad6ec9ffda6e236a4bf5f7dd8eb72447ac0 Mon Sep 17 00:00:00 2001 From: Econa77 Date: Sun, 1 May 2016 21:35:47 +0900 Subject: [PATCH 015/116] Fix auto layout size calculation --- ActiveLabel/ActiveLabel.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 80c98d85..31924a22 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -130,6 +130,14 @@ public protocol ActiveLabelDelegate: class { return self } + // MARK: - Auto layout + public override func intrinsicContentSize() -> CGSize { + let superSize = super.intrinsicContentSize() + textContainer.size = CGSize(width: superSize.width, height: CGFloat.max) + let size = layoutManager.usedRectForTextContainer(textContainer) + return CGSize(width: size.width, height: ceil(size.height)) + } + // MARK: - touch events func onTouch(touch: UITouch) -> Bool { let location = touch.locationInView(self) From 8ff649a053bfebdb94b4b78719ed15c547057c77 Mon Sep 17 00:00:00 2001 From: ShunsukeFurubayashi Date: Wed, 11 May 2016 18:24:30 +0900 Subject: [PATCH 016/116] Clear text when set the empty string --- ActiveLabel/ActiveLabel.swift | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 80c98d85..a3024a5e 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -208,18 +208,17 @@ public protocol ActiveLabelDelegate: class { private func updateTextStorage(parseText parseText: Bool = true) { if _customizing { return } // clean up previous active elements - guard let attributedText = attributedText - where attributedText.length > 0 else { + guard let attributedText = attributedText where attributedText.length > 0 else { + clearActiveElements() + textStorage.setAttributedString(NSAttributedString()) + setNeedsDisplay() return } let mutAttrString = addLineBreak(attributedText) if parseText { - selectedElement = nil - for (type, _) in activeElements { - activeElements[type]?.removeAll() - } + clearActiveElements() parseTextAndExtractActiveElements(mutAttrString) } @@ -227,7 +226,14 @@ public protocol ActiveLabelDelegate: class { self.textStorage.setAttributedString(mutAttrString) self.setNeedsDisplay() } - + + private func clearActiveElements() { + selectedElement = nil + for (type, _) in activeElements { + activeElements[type]?.removeAll() + } + } + private func textOrigin(inRect rect: CGRect) -> CGPoint { let usedRect = layoutManager.usedRectForTextContainer(textContainer) heightCorrection = (rect.height - usedRect.height)/2 From e3aa02e640ee7156c5325027c0d6722f3909fbb2 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Tue, 31 May 2016 15:08:56 +0200 Subject: [PATCH 017/116] Fix #9 - Allow dots in mentions --- ActiveLabel/RegexParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 52b14d95..f207db98 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -15,7 +15,7 @@ struct RegexParser { "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[\\p{L}0-9_]*", options: [.CaseInsensitive]) - static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_]*", options: [.CaseInsensitive]); + static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_.]*", options: [.CaseInsensitive]); static let urlDetector = try? NSRegularExpression(pattern: urlPattern, options: [.CaseInsensitive]) static func getMentions(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { @@ -33,4 +33,4 @@ struct RegexParser { return urlDetector.matchesInString(text, options: [], range: range) } -} \ No newline at end of file +} From ccda9552415272d8566206a60e69ab60744aa2b8 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Tue, 31 May 2016 15:53:44 +0200 Subject: [PATCH 018/116] Revert "Fix #9 - Allow dots in mentions" This reverts commit e3aa02e640ee7156c5325027c0d6722f3909fbb2. --- ActiveLabel/RegexParser.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index f207db98..52b14d95 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -15,7 +15,7 @@ struct RegexParser { "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[\\p{L}0-9_]*", options: [.CaseInsensitive]) - static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_.]*", options: [.CaseInsensitive]); + static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_]*", options: [.CaseInsensitive]); static let urlDetector = try? NSRegularExpression(pattern: urlPattern, options: [.CaseInsensitive]) static func getMentions(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { @@ -33,4 +33,4 @@ struct RegexParser { return urlDetector.matchesInString(text, options: [], range: range) } -} +} \ No newline at end of file From a2e80fa25180ef01d67ff51c28151d8225d10c08 Mon Sep 17 00:00:00 2001 From: Adriaan iMac Date: Fri, 10 Jun 2016 11:54:07 +0200 Subject: [PATCH 019/116] Fix: Super obscure bug that would truncate text in some edge cases while using Auto-layout constraints --- ActiveLabel/ActiveLabel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index f2bd9e44..c0f2abbb 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -135,7 +135,7 @@ public protocol ActiveLabelDelegate: class { let superSize = super.intrinsicContentSize() textContainer.size = CGSize(width: superSize.width, height: CGFloat.max) let size = layoutManager.usedRectForTextContainer(textContainer) - return CGSize(width: size.width, height: ceil(size.height)) + return CGSize(width: ceil(size.width), height: ceil(size.height)) } // MARK: - touch events From 1ecaecd0a45b8f244a9b7de87eb78ea4b54c62e9 Mon Sep 17 00:00:00 2001 From: Adriaan iMac Date: Fri, 10 Jun 2016 12:27:32 +0200 Subject: [PATCH 020/116] Bumb version to 0.5.1 --- ActiveLabel.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 5ec89de1..d1e95c25 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.5.0' + s.version = '0.5.1' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' s.license = { :type => 'MIT', :file => 'LICENSE' } s.platform = :ios, '8.0' - s.source = { :git => 'https://github.com/optonaut/ActiveLabel.swift.git', :tag => s.version.to_s } + s.source = { :git => 'https://github.com/nxtstep/ActiveLabel.swift.git', :tag => s.version.to_s } s.summary = 'UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift' s.description = <<-DESC UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift From 337d4763f0ebc346e53e53c6d3799220d4b707f2 Mon Sep 17 00:00:00 2001 From: Adriaan iMac Date: Fri, 10 Jun 2016 17:32:24 +0200 Subject: [PATCH 021/116] Reverse podspec source --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index d1e95c25..ae5bbcb8 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -6,7 +6,7 @@ Pod::Spec.new do |s| s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' s.license = { :type => 'MIT', :file => 'LICENSE' } s.platform = :ios, '8.0' - s.source = { :git => 'https://github.com/nxtstep/ActiveLabel.swift.git', :tag => s.version.to_s } + s.source = { :git => 'https://github.com/optonaut/ActiveLabel.swift.git', :tag => s.version.to_s } s.summary = 'UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift' s.description = <<-DESC UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift From fa897a0d1a9db9faf62a694caf844892527cf43d Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Thu, 16 Jun 2016 14:24:44 +0200 Subject: [PATCH 022/116] Avoid force unwraps that could crash on tests - Fixes #87 --- ActiveLabelTests/ActiveTypeTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 508cf743..e97fdc81 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -29,8 +29,8 @@ class ActiveTypeTests: XCTestCase { return label.activeElements.flatMap({$0.1.flatMap({$0.element})}) } - var currentElementString: String { - let currentElement = activeElements.first! + var currentElementString: String? { + guard let currentElement = activeElements.first else { return nil } switch currentElement { case .Mention(let mention): return mention @@ -43,8 +43,8 @@ class ActiveTypeTests: XCTestCase { } } - var currentElementType: ActiveType { - let currentElement = activeElements.first! + var currentElementType: ActiveType? { + guard let currentElement = activeElements.first else { return nil } switch currentElement { case .Mention: return .Mention From a741ac8463cd5efe33f6a27001a9518c3a207730 Mon Sep 17 00:00:00 2001 From: Alejandro Moya Date: Thu, 21 Jul 2016 14:29:03 -0300 Subject: [PATCH 023/116] added minimumLineHeight and applied ceil on height added minimumLineHeight to be able to handle emojis properly and applied ceil on intrinsicContentSize() to avoid glitches created by decimal pixels --- ActiveLabel/ActiveLabel.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index c0f2abbb..765b8fcd 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -39,7 +39,9 @@ public protocol ActiveLabelDelegate: class { @IBInspectable public var lineSpacing: Float = 0 { didSet { updateTextStorage(parseText: false) } } - + @IBInspectable public var minimumLineHeight: Float = 0 { + didSet { updateTextStorage(parseText: false) } + } // MARK: - public methods public func handleMentionTap(handler: (String) -> ()) { mentionTapHandler = handler @@ -135,7 +137,7 @@ public protocol ActiveLabelDelegate: class { let superSize = super.intrinsicContentSize() textContainer.size = CGSize(width: superSize.width, height: CGFloat.max) let size = layoutManager.usedRectForTextContainer(textContainer) - return CGSize(width: ceil(size.width), height: ceil(size.height)) + return CGSize(width: size.width, height: ceil(size.height)) } // MARK: - touch events @@ -306,7 +308,7 @@ public protocol ActiveLabelDelegate: class { paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = CGFloat(lineSpacing) - + paragraphStyle.minimumLineHeight = CGFloat(minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14) attributes[NSParagraphStyleAttributeName] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) From 93c0f36fca2a7d19f61264830d833fedecc07581 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 20:25:40 +0200 Subject: [PATCH 024/116] Support for new .Custom ActiveType --- ActiveLabel/ActiveLabel.swift | 101 +++++++++++++++------- ActiveLabel/ActiveType.swift | 121 ++++++++++++++++++--------- ActiveLabel/RegexParser.swift | 30 ++----- ActiveLabelDemo/ViewController.swift | 23 ++++- 4 files changed, 181 insertions(+), 94 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index c0f2abbb..f5cd2b74 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -13,10 +13,14 @@ public protocol ActiveLabelDelegate: class { func didSelectText(text: String, type: ActiveType) } +typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) + @IBDesignable public class ActiveLabel: UILabel { // MARK: - public properties public weak var delegate: ActiveLabelDelegate? + + public var enabledTypes: [ActiveType] = [.Mention, .Hashtag, .URL] @IBInspectable public var mentionColor: UIColor = .blueColor() { didSet { updateTextStorage(parseText: false) } @@ -36,6 +40,12 @@ public protocol ActiveLabelDelegate: class { @IBInspectable public var URLSelectedColor: UIColor? { didSet { updateTextStorage(parseText: false) } } + public var customColor: [ActiveType : UIColor] = [:] { + didSet { updateTextStorage(parseText: false) } + } + public var customSelectedColor: [ActiveType : UIColor] = [:] { + didSet { updateTextStorage(parseText: false) } + } @IBInspectable public var lineSpacing: Float = 0 { didSet { updateTextStorage(parseText: false) } } @@ -53,6 +63,10 @@ public protocol ActiveLabelDelegate: class { urlTapHandler = handler } + public func handleCustomTap(for type: ActiveType, handler: (String) -> ()) { + customTapHandlers[type] = handler + } + public func filterMention(predicate: (String) -> Bool) { mentionFilterPredicate = predicate updateTextStorage() @@ -122,7 +136,7 @@ public protocol ActiveLabelDelegate: class { // MARK: - customzation - public func customize(block: (label: ActiveLabel) -> ()) -> ActiveLabel{ + public func customize(block: (label: ActiveLabel) -> ()) -> ActiveLabel { _customizing = true block(label: self) _customizing = false @@ -163,7 +177,7 @@ public protocol ActiveLabelDelegate: class { case .Mention(let userHandle): didTapMention(userHandle) case .Hashtag(let hashtag): didTapHashtag(hashtag) case .URL(let url): didTapStringURL(url) - case .None: () + case .Custom(let element): didTap(element, for: selectedElement.type) } let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))) @@ -184,25 +198,23 @@ public protocol ActiveLabelDelegate: class { // MARK: - private properties private var _customizing: Bool = true + private var defaultCustomColor: UIColor = .blackColor() private var mentionTapHandler: ((String) -> ())? private var hashtagTapHandler: ((String) -> ())? private var urlTapHandler: ((NSURL) -> ())? + private var customTapHandlers: [ActiveType : ((String) -> ())] = [:] private var mentionFilterPredicate: ((String) -> Bool)? private var hashtagFilterPredicate: ((String) -> Bool)? - private var selectedElement: (range: NSRange, element: ActiveElement)? + private var selectedElement: ElementTuple? private var heightCorrection: CGFloat = 0 private lazy var textStorage = NSTextStorage() private lazy var layoutManager = NSLayoutManager() private lazy var textContainer = NSTextContainer() - internal lazy var activeElements: [ActiveType: [(range: NSRange, element: ActiveElement)]] = [ - .Mention: [], - .Hashtag: [], - .URL: [], - ] - + private lazy var activeElements = [ActiveType: [ElementTuple]]() + // MARK: - helper functions private func setupLabel() { textStorage.addLayoutManager(layoutManager) @@ -230,9 +242,9 @@ public protocol ActiveLabelDelegate: class { parseTextAndExtractActiveElements(mutAttrString) } - self.addLinkAttribute(mutAttrString) - self.textStorage.setAttributedString(mutAttrString) - self.setNeedsDisplay() + addLinkAttribute(mutAttrString) + textStorage.setAttributedString(mutAttrString) + setNeedsDisplay() } private func clearActiveElements() { @@ -266,7 +278,7 @@ public protocol ActiveLabelDelegate: class { case .Mention: attributes[NSForegroundColorAttributeName] = mentionColor case .Hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor case .URL: attributes[NSForegroundColorAttributeName] = URLColor - case .None: () + case .Custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } for element in elements { @@ -282,16 +294,29 @@ public protocol ActiveLabelDelegate: class { let textRange = NSRange(location: 0, length: textLength) //URLS - let urlElements = ActiveBuilder.createURLElements(fromText: textString, range: textRange) - activeElements[.URL]?.appendContentsOf(urlElements) + if enabledTypes.contains(.URL) { + let urlElements = ActiveBuilder.createElements(.URL, from: textString, range: textRange, filterPredicate: nil) + activeElements[.URL] = urlElements + } //HASHTAGS - let hashtagElements = ActiveBuilder.createHashtagElements(fromText: textString, range: textRange, filterPredicate: hashtagFilterPredicate) - activeElements[.Hashtag]?.appendContentsOf(hashtagElements) + if enabledTypes.contains(.Hashtag) { + let hashtagElements = ActiveBuilder.createElements(.Hashtag, from: textString, range: textRange, filterPredicate: nil) + activeElements[.Hashtag] = hashtagElements + } //MENTIONS - let mentionElements = ActiveBuilder.createMentionElements(fromText: textString, range: textRange, filterPredicate: mentionFilterPredicate) - activeElements[.Mention]?.appendContentsOf(mentionElements) + if enabledTypes.contains(.Mention) { + let mentionElements = ActiveBuilder.createElements(.Mention, from: textString, range: textRange, filterPredicate: nil) + activeElements[.Mention] = mentionElements + } + + //CUSTOM + let customTypes = enabledTypes.filter { $0.isCustom } + for customType in customTypes { + let elements = ActiveBuilder.createElements(customType, from: textString, range: textRange, filterPredicate: nil) + activeElements[customType] = elements + } } @@ -319,20 +344,28 @@ public protocol ActiveLabelDelegate: class { } var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil) + let type = selectedElement.type + if isSelected { - switch selectedElement.element { - case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionSelectedColor ?? mentionColor - case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagSelectedColor ?? hashtagColor - case .URL(_): attributes[NSForegroundColorAttributeName] = URLSelectedColor ?? URLColor - case .None: () + let selectedColor: UIColor + switch type { + case .Mention: selectedColor = mentionSelectedColor ?? mentionColor + case .Hashtag: selectedColor = hashtagSelectedColor ?? hashtagColor + case .URL: selectedColor = URLSelectedColor ?? URLColor + case .Custom: + let possibleSelectedColor = customSelectedColor[selectedElement.type] ?? customColor[selectedElement.type] + selectedColor = possibleSelectedColor ?? defaultCustomColor } + attributes[NSForegroundColorAttributeName] = selectedColor } else { - switch selectedElement.element { - case .Mention(_): attributes[NSForegroundColorAttributeName] = mentionColor - case .Hashtag(_): attributes[NSForegroundColorAttributeName] = hashtagColor - case .URL(_): attributes[NSForegroundColorAttributeName] = URLColor - case .None: () + let unselectedColor: UIColor + switch type { + case .Mention: unselectedColor = mentionColor + case .Hashtag: unselectedColor = hashtagColor + case .URL: unselectedColor = URLColor + case .Custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor } + attributes[NSForegroundColorAttributeName] = unselectedColor } textStorage.addAttributes(attributes, range: selectedElement.range) @@ -340,7 +373,7 @@ public protocol ActiveLabelDelegate: class { setNeedsDisplay() } - private func elementAtLocation(location: CGPoint) -> (range: NSRange, element: ActiveElement)? { + private func elementAtLocation(location: CGPoint) -> ElementTuple? { guard textStorage.length > 0 else { return nil } @@ -413,6 +446,14 @@ public protocol ActiveLabelDelegate: class { } urlHandler(url) } + + private func didTap(element: String, for type: ActiveType) { + guard let elementHandler = customTapHandlers[type] else { + delegate?.didSelectText(element, type: type) + return + } + elementHandler(element) + } } extension ActiveLabel: UIGestureRecognizerDelegate { diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 11b3fbf7..6b7bdfdf 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -12,71 +12,112 @@ enum ActiveElement { case Mention(String) case Hashtag(String) case URL(String) - case None + case Custom(String) + + static func create(with activeType: ActiveType, text: String) -> ActiveElement { + switch activeType { + case .Mention: return Mention(text) + case .Hashtag: return Hashtag(text) + case .URL: return URL(text) + case .Custom: return Custom(text) + } + } } public enum ActiveType { case Mention case Hashtag case URL - case None + case Custom(pattern: String) + + var isCustom: Bool { + if case .Custom = self { + return true + } + return false + } + var pattern: String { + switch self { + case .Mention: return RegexParser.mentionPattern + case .Hashtag: return RegexParser.hashtagPattern + case .URL: return RegexParser.urlPattern + case .Custom(let regex): return regex + } + } +} + +extension ActiveType: Hashable, Equatable { + public var hashValue: Int { + switch self { + case .Mention: return -1 + case .Hashtag: return -2 + case .URL: return -3 + case .Custom(let regex): return regex.hashValue + } + } +} + +public func ==(lhs: ActiveType, rhs: ActiveType) -> Bool { + switch (lhs, rhs) { + case (.Mention, .Mention): return true + case (.Hashtag, .Hashtag): return true + case (.URL, .URL): return true + case (.Custom(let pattern1), .Custom(let pattern2)): return pattern1 == pattern2 + default: return false + } } typealias ActiveFilterPredicate = (String -> Bool) struct ActiveBuilder { - - static func createMentionElements(fromText text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [(range: NSRange, element: ActiveElement)] { - let mentions = RegexParser.getMentions(fromText: text, range: range) + + static func createElements(type: ActiveType, from text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + switch type { + case .Mention, .Hashtag: + return createElementsIgnoringFirstCharacter(from: text, for: type, range: range, filterPredicate: filterPredicate) + case .URL, .Custom: + return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) + } + } + + private static func createElements(from text: String, + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) let nsstring = text as NSString - var elements: [(range: NSRange, element: ActiveElement)] = [] - - for mention in mentions where mention.range.length > 2 { - let range = NSRange(location: mention.range.location + 1, length: mention.range.length - 1) - var word = nsstring.substringWithRange(range) - if word.hasPrefix("@") { - word.removeAtIndex(word.startIndex) - } + var elements: [ElementTuple] = [] - if filterPredicate?(word) ?? true { - let element = ActiveElement.Mention(word) - elements.append((mention.range, element)) - } + for match in matches where match.range.length > 2 { + let word = nsstring.substringWithRange(match.range) + .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) } return elements } - - static func createHashtagElements(fromText text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [(range: NSRange, element: ActiveElement)] { - let hashtags = RegexParser.getHashtags(fromText: text, range: range) + + private static func createElementsIgnoringFirstCharacter(from text: String, + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) let nsstring = text as NSString - var elements: [(range: NSRange, element: ActiveElement)] = [] - - for hashtag in hashtags where hashtag.range.length > 2 { - let range = NSRange(location: hashtag.range.location + 1, length: hashtag.range.length - 1) + var elements: [ElementTuple] = [] + + for match in matches where match.range.length > 2 { + let range = NSRange(location: match.range.location + 1, length: match.range.length - 1) var word = nsstring.substringWithRange(range) - if word.hasPrefix("#") { + if word.hasPrefix("@") { word.removeAtIndex(word.startIndex) } if filterPredicate?(word) ?? true { - let element = ActiveElement.Hashtag(word) - elements.append((hashtag.range, element)) + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) } } return elements - } - - static func createURLElements(fromText text: String, range: NSRange) -> [(range: NSRange, element: ActiveElement)] { - let urls = RegexParser.getURLs(fromText: text, range: range) - let nsstring = text as NSString - var elements: [(range: NSRange, element: ActiveElement)] = [] - for url in urls where url.range.length > 2 { - let word = nsstring.substringWithRange(url.range) - .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - let element = ActiveElement.URL(word) - elements.append((url.range, element)) - } - return elements } } diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 52b14d95..809badcc 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -9,28 +9,16 @@ import Foundation struct RegexParser { - + + static let hashtagPattern = "(?:^|\\s|$)#[\\p{L}0-9_]*" + static let mentionPattern = "(?:^|\\s|$|[.])@[\\p{L}0-9_]*" static let urlPattern = "(^|[\\s.:;?\\-\\]<\\(])" + - "((https?://|www\\.|pic\\.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + + "((https?://|www\\.|pic\\.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" - - static let hashtagRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$)#[\\p{L}0-9_]*", options: [.CaseInsensitive]) - static let mentionRegex = try? NSRegularExpression(pattern: "(?:^|\\s|$|[.])@[\\p{L}0-9_]*", options: [.CaseInsensitive]); - static let urlDetector = try? NSRegularExpression(pattern: urlPattern, options: [.CaseInsensitive]) - - static func getMentions(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { - guard let mentionRegex = mentionRegex else { return [] } - return mentionRegex.matchesInString(text, options: [], range: range) - } - - static func getHashtags(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { - guard let hashtagRegex = hashtagRegex else { return [] } - return hashtagRegex.matchesInString(text, options: [], range: range) - } - - static func getURLs(fromText text: String, range: NSRange) -> [NSTextCheckingResult] { - guard let urlDetector = urlDetector else { return [] } - return urlDetector.matchesInString(text, options: [], range: range) + + + static func getElements(from text: String, with pattern: String, range: NSRange) -> [NSTextCheckingResult]{ + guard let elementRegex = try? NSRegularExpression(pattern: pattern, options: [.CaseInsensitive]) else { return [] } + return elementRegex.matchesInString(text, options: [], range: range) } - } \ No newline at end of file diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index c999000d..bb864cc5 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -15,9 +15,16 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - + + let customType = ActiveType.Custom(pattern: "\\sare\\b") //Looks for "are" + let customType2 = ActiveType.Custom(pattern: "\\sit\\b") //Looks for "it" + + label.enabledTypes.append(customType) + label.enabledTypes.append(customType2) + label.customize { label in - label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like this one: http://optonaut.co." + label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like" + + " this one: http://optonaut.co. Now it also supports custom patterns -> are" label.numberOfLines = 0 label.lineSpacing = 4 @@ -30,8 +37,18 @@ class ViewController: UIViewController { label.handleMentionTap { self.alert("Mention", message: $0) } label.handleHashtagTap { self.alert("Hashtag", message: $0) } label.handleURLTap { self.alert("URL", message: $0.absoluteString) } + + //Custom types + + label.customColor[customType] = UIColor.purpleColor() + label.customSelectedColor[customType] = UIColor.greenColor() + label.customColor[customType2] = UIColor.magentaColor() + label.customSelectedColor[customType2] = UIColor.greenColor() + + label.handleCustomTap(for: customType) { self.alert("Custom type", message: $0) } + label.handleCustomTap(for: customType2) { self.alert("Custom type", message: $0) } } - + label.frame = CGRect(x: 20, y: 40, width: view.frame.width - 40, height: 300) view.addSubview(label) From 8cd014ad531f8c310f2123680249323fc48ee715 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 20:48:06 +0200 Subject: [PATCH 025/116] Add tests for new .Custom ActiveType --- ActiveLabel/ActiveLabel.swift | 6 +- ActiveLabel/ActiveType.swift | 9 +- ActiveLabelTests/ActiveTypeTests.swift | 132 +++++++++++++++++++++---- 3 files changed, 125 insertions(+), 22 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index f5cd2b74..6adafbd3 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -213,7 +213,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy private lazy var textStorage = NSTextStorage() private lazy var layoutManager = NSLayoutManager() private lazy var textContainer = NSTextContainer() - private lazy var activeElements = [ActiveType: [ElementTuple]]() + lazy var activeElements = [ActiveType: [ElementTuple]]() // MARK: - helper functions private func setupLabel() { @@ -301,13 +301,13 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy //HASHTAGS if enabledTypes.contains(.Hashtag) { - let hashtagElements = ActiveBuilder.createElements(.Hashtag, from: textString, range: textRange, filterPredicate: nil) + let hashtagElements = ActiveBuilder.createElements(.Hashtag, from: textString, range: textRange, filterPredicate: hashtagFilterPredicate) activeElements[.Hashtag] = hashtagElements } //MENTIONS if enabledTypes.contains(.Mention) { - let mentionElements = ActiveBuilder.createElements(.Mention, from: textString, range: textRange, filterPredicate: nil) + let mentionElements = ActiveBuilder.createElements(.Mention, from: textString, range: textRange, filterPredicate: mentionFilterPredicate) activeElements[.Mention] = mentionElements } diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 6b7bdfdf..f954eb1e 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -91,8 +91,10 @@ struct ActiveBuilder { for match in matches where match.range.length > 2 { let word = nsstring.substringWithRange(match.range) .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) + if filterPredicate?(word) ?? true { + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) + } } return elements } @@ -111,6 +113,9 @@ struct ActiveBuilder { if word.hasPrefix("@") { word.removeAtIndex(word.startIndex) } + else if word.hasPrefix("#") { + word.removeAtIndex(word.startIndex) + } if filterPredicate?(word) ?? true { let element = ActiveElement.create(with: type, text: word) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index e97fdc81..4f68ea7e 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -16,7 +16,7 @@ func ==(a: ActiveElement, b: ActiveElement) -> Bool { case (.Mention(let a), .Mention(let b)) where a == b: return true case (.Hashtag(let a), .Hashtag(let b)) where a == b: return true case (.URL(let a), .URL(let b)) where a == b: return true - case (.None, .None): return true + case (.Custom(let a), .Custom(let b)) where a == b: return true default: return false } } @@ -24,6 +24,7 @@ func ==(a: ActiveElement, b: ActiveElement) -> Bool { class ActiveTypeTests: XCTestCase { let label = ActiveLabel() + let customEmptyType = ActiveType.Custom(pattern: "") var activeElements: [ActiveElement] { return label.activeElements.flatMap({$0.1.flatMap({$0.element})}) @@ -32,33 +33,26 @@ class ActiveTypeTests: XCTestCase { var currentElementString: String? { guard let currentElement = activeElements.first else { return nil } switch currentElement { - case .Mention(let mention): - return mention - case .Hashtag(let hashtag): - return hashtag - case .URL(let url): - return url - case .None: - return "" + case .Mention(let mention): return mention + case .Hashtag(let hashtag): return hashtag + case .URL(let url): return url + case .Custom(let element): return element } } var currentElementType: ActiveType? { guard let currentElement = activeElements.first else { return nil } switch currentElement { - case .Mention: - return .Mention - case .Hashtag: - return .Hashtag - case .URL: - return .URL - case .None: - return .None + case .Mention: return .Mention + case .Hashtag: return .Hashtag + case .URL: return .URL + case .Custom: return customEmptyType } } override func setUp() { super.setUp() + label.enabledTypes = [.Mention, .Hashtag, .URL] // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -206,6 +200,29 @@ class ActiveTypeTests: XCTestCase { XCTAssertEqual(activeElements.count, 0) } + func testCustomType() { + let newType = ActiveType.Custom(pattern: "\\sare\\b") + label.enabledTypes.append(newType) + + label.text = "we are one" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "are") + XCTAssertEqual(currentElementType, customEmptyType) + + label.text = "are. are" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "are") + XCTAssertEqual(currentElementType, customEmptyType) + + label.text = "helloare are" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "are") + XCTAssertEqual(currentElementType, customEmptyType) + + label.text = "google" + XCTAssertEqual(activeElements.count, 0) + } + func testFiltering() { label.text = "@user #tag" XCTAssertEqual(activeElements.count, 2) @@ -228,4 +245,85 @@ class ActiveTypeTests: XCTestCase { label.text = "wwwbar" XCTAssertEqual(activeElements.count, 0) } + + func testOnlyMentionsEnabled() { + label.enabledTypes = [.Mention] + + label.text = "@user #hashtag" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "user") + XCTAssertEqual(currentElementType, ActiveType.Mention) + + label.text = "http://www.google.com" + XCTAssertEqual(activeElements.count, 0) + + label.text = "#somehashtag" + XCTAssertEqual(activeElements.count, 0) + + label.text = "@userNumberOne #hashtag http://www.google.com @anotheruser" + XCTAssertEqual(activeElements.count, 2) + XCTAssertEqual(currentElementString, "userNumberOne") + XCTAssertEqual(currentElementType, ActiveType.Mention) + } + + func testOnlyHashtagEnabled() { + label.enabledTypes = [.Hashtag] + + label.text = "@user #hashtag" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "hashtag") + XCTAssertEqual(currentElementType, ActiveType.Hashtag) + + label.text = "http://www.google.com" + XCTAssertEqual(activeElements.count, 0) + + label.text = "@someuser" + XCTAssertEqual(activeElements.count, 0) + + label.text = "#hashtagNumberOne #hashtag http://www.google.com @anotheruser" + XCTAssertEqual(activeElements.count, 2) + XCTAssertEqual(currentElementString, "hashtagNumberOne") + XCTAssertEqual(currentElementType, ActiveType.Hashtag) + } + + func testOnlyURLsEnabled() { + label.enabledTypes = [.URL] + + label.text = "http://www.google.com #hello" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "http://www.google.com") + XCTAssertEqual(currentElementType, ActiveType.URL) + + label.text = "@user" + XCTAssertEqual(activeElements.count, 0) + + label.text = "#somehashtag" + XCTAssertEqual(activeElements.count, 0) + + label.text = " http://www.apple.com @userNumberOne #hashtag http://www.google.com @anotheruser" + XCTAssertEqual(activeElements.count, 2) + XCTAssertEqual(currentElementString, "http://www.apple.com") + XCTAssertEqual(currentElementType, ActiveType.URL) + } + + func testOnlyCustomEnabled() { + let newType = ActiveType.Custom(pattern: "\\sare\\b") + label.enabledTypes = [newType] + + label.text = "http://www.google.com are #hello" + XCTAssertEqual(activeElements.count, 1) + XCTAssertEqual(currentElementString, "are") + XCTAssertEqual(currentElementType, customEmptyType) + + label.text = "@user" + XCTAssertEqual(activeElements.count, 0) + + label.text = "#somehashtag" + XCTAssertEqual(activeElements.count, 0) + + label.text = " http://www.apple.com are @userNumberOne #hashtag http://www.google.com are @anotheruser" + XCTAssertEqual(activeElements.count, 2) + XCTAssertEqual(currentElementString, "are") + XCTAssertEqual(currentElementType, customEmptyType) + } } From b98394cfca41fa37efff4dc3b9e84dc05a02bbf0 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 20:56:40 +0200 Subject: [PATCH 026/116] Update travis to use xcode7.3 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b4f9e4a9..330e43fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,5 @@ language: objective-c xcode_project: ActiveLabel.xcodeproj xcode_scheme: ActiveLabel -osx_image: xcode7 -xcode_sdk: iphonesimulator9.0 +osx_image: xcode7.3 +xcode_sdk: iphonesimulator9.3 From c173e711e3e89e92d20be31137de9cb7b9d4723d Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 21:00:11 +0200 Subject: [PATCH 027/116] Improve parsing logic --- ActiveLabel/ActiveLabel.swift | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 6adafbd3..24dff17f 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -292,30 +292,16 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let textString = attrString.string let textLength = textString.utf16.count let textRange = NSRange(location: 0, length: textLength) - - //URLS - if enabledTypes.contains(.URL) { - let urlElements = ActiveBuilder.createElements(.URL, from: textString, range: textRange, filterPredicate: nil) - activeElements[.URL] = urlElements - } - - //HASHTAGS - if enabledTypes.contains(.Hashtag) { - let hashtagElements = ActiveBuilder.createElements(.Hashtag, from: textString, range: textRange, filterPredicate: hashtagFilterPredicate) - activeElements[.Hashtag] = hashtagElements - } - //MENTIONS - if enabledTypes.contains(.Mention) { - let mentionElements = ActiveBuilder.createElements(.Mention, from: textString, range: textRange, filterPredicate: mentionFilterPredicate) - activeElements[.Mention] = mentionElements - } - - //CUSTOM - let customTypes = enabledTypes.filter { $0.isCustom } - for customType in customTypes { - let elements = ActiveBuilder.createElements(customType, from: textString, range: textRange, filterPredicate: nil) - activeElements[customType] = elements + for type in enabledTypes { + var filter: ((String) -> Bool)? = nil + if type == .Mention { + filter = mentionFilterPredicate + } else if type == .Hashtag { + filter = hashtagFilterPredicate + } + let hashtagElements = ActiveBuilder.createElements(type, from: textString, range: textRange, filterPredicate: filter) + activeElements[type] = hashtagElements } } From cf19a968c2f139382d6a848bb40f84c14330fc06 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 21:28:38 +0200 Subject: [PATCH 028/116] Update README with Custom Types documentation --- README.md | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 928e2141..ab9361e7 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # ActiveLabel.swift [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/optonaut/ActiveLabel.swift.svg)](https://travis-ci.org/optonaut/ActiveLabel.swift) -UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift +UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift ## Features * Swift 2+ -* Support for **Hashtags, Mentions and Links** +* Default support for **Hashtags, Mentions, Links** +* Support for **custom types** via regex +* Ability to enable highlighting only for the desired types * Super easy to use and lightweight * Works as `UILabel` drop-in replacement * Well tested and documented @@ -20,6 +22,7 @@ import ActiveLabel let label = ActiveLabel() label.numberOfLines = 0 +label.enabledTypes = [.Mention, .Hashtag, .URL] label.text = "This is a post with #hashtags and a @userhandle." label.textColor = .blackColor() label.handleHashtagTap { hashtag in @@ -27,6 +30,31 @@ label.handleHashtagTap { hashtag in } ``` +## Custom types + +```swift + let customType = ActiveType.Custom(pattern: "\\swith\\b") //Regex that looks for "with" + label.enabledTypes = [.Mention, .Hashtag, .URL, customType] + + label.customColor[customType] = UIColor.purpleColor() + label.customSelectedColor[customType] = UIColor.greenColor() + + label.handleCustomTap(for: customType) { element in + print("Custom type tapped: \(element)") + } +``` + +## Enable/disable highlighting + +By default, an ActiveLabel instance has the following configuration + +```swift + label.enabledTypes = [.Mention, .Hashtag, .URL] +``` + +But feel free to enable/disable to fit your requirements + + ## Batched customization When using ActiveLabel, it is recommended to use the `customize(block:)` method to customize it. The reason is that ActiveLabel is reacting to each property that you set. So if you set 3 properties, the textContainer is refreshed 3 times. @@ -60,6 +88,8 @@ Example: ##### `hashtagSelectedColor: UIColor?` ##### `URLColor: UIColor = .blueColor()` ##### `URLSelectedColor: UIColor?` +#### `customColor: [ActiveType : UIColor]` +#### `customSelectedColor: [ActiveType : UIColor]` ##### `lineSpacing: Float?` ##### `handleMentionTap: (String) -> ()` @@ -80,6 +110,12 @@ label.handleHashtagTap { hashtag in print("\(hashtag) tapped") } label.handleURLTap { url in UIApplication.sharedApplication().openURL(url) } ``` +##### `handleCustomTap(for type: ActiveType, handler: (String) -> ())` + +```swift +label.handleCustomTap(for: customType) { element in print("\(element) tapped") } +``` + ##### `filterHashtag: (String) -> Bool` ```swift From f652c62b6fa08a6756c5988ddef1144fe8e9e060 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 21:33:38 +0200 Subject: [PATCH 029/116] Remove unused ActiveType property --- ActiveLabel/ActiveType.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index f954eb1e..9d26366a 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -30,12 +30,6 @@ public enum ActiveType { case URL case Custom(pattern: String) - var isCustom: Bool { - if case .Custom = self { - return true - } - return false - } var pattern: String { switch self { case .Mention: return RegexParser.mentionPattern From 162f8f81334130641a91ede8f2032f389302b336 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 21:42:55 +0200 Subject: [PATCH 030/116] Update podspec --- ActiveLabel.podspec | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index ae5bbcb8..e988d139 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,19 +1,21 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.5.1' + s.version = '0.6.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' s.license = { :type => 'MIT', :file => 'LICENSE' } s.platform = :ios, '8.0' s.source = { :git => 'https://github.com/optonaut/ActiveLabel.swift.git', :tag => s.version.to_s } - s.summary = 'UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift' + s.summary = 'UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift' s.description = <<-DESC - UILabel drop-in replacement supporting Hashtags (#), Mentions (@) and URLs (http://) written in Swift + UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 2 (Xcode 7 GM) - * Support for Hashtags, Mentions and Links + * Up-to-date: Swift 2+ + * Default support for Hashtags, Mentions, Links + * Support for custom types via regex + * Ability to enable highlighting only for the desired types * Super easy to use and lightweight * Works as UILabel drop-in replacement * Well tested and documented From fda2b1d071eceea88e969ebea1ac858c97a3f997 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 22:09:45 +0200 Subject: [PATCH 031/116] Update to Swift3.0 --- ActiveLabel.xcodeproj/project.pbxproj | 5 + ActiveLabel/ActiveLabel.swift | 169 +++++++++++++------------- ActiveLabel/ActiveType.swift | 66 +++++----- ActiveLabel/RegexParser.swift | 8 +- ActiveLabelDemo/AppDelegate.swift | 12 +- ActiveLabelDemo/ViewController.swift | 22 ++-- 6 files changed, 144 insertions(+), 138 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 88a4c04b..ce2eea3f 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -208,6 +208,7 @@ TargetAttributes = { 8F0249A11B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0800; }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; @@ -427,6 +428,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -445,6 +447,7 @@ PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -477,6 +480,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -489,6 +493,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 24dff17f..dc820638 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -10,7 +10,7 @@ import Foundation import UIKit public protocol ActiveLabelDelegate: class { - func didSelectText(text: String, type: ActiveType) + func didSelect(_ text: String, type: ActiveType) } typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @@ -20,21 +20,21 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - public properties public weak var delegate: ActiveLabelDelegate? - public var enabledTypes: [ActiveType] = [.Mention, .Hashtag, .URL] + public var enabledTypes: [ActiveType] = [.mention, .hashtag, .url] - @IBInspectable public var mentionColor: UIColor = .blueColor() { + @IBInspectable public var mentionColor: UIColor = .blue() { didSet { updateTextStorage(parseText: false) } } @IBInspectable public var mentionSelectedColor: UIColor? { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var hashtagColor: UIColor = .blueColor() { + @IBInspectable public var hashtagColor: UIColor = .blue() { didSet { updateTextStorage(parseText: false) } } @IBInspectable public var hashtagSelectedColor: UIColor? { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var URLColor: UIColor = .blueColor() { + @IBInspectable public var URLColor: UIColor = .blue() { didSet { updateTextStorage(parseText: false) } } @IBInspectable public var URLSelectedColor: UIColor? { @@ -51,15 +51,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } // MARK: - public methods - public func handleMentionTap(handler: (String) -> ()) { + public func handleMentionTap(_ handler: (String) -> ()) { mentionTapHandler = handler } - public func handleHashtagTap(handler: (String) -> ()) { + public func handleHashtagTap(_ handler: (String) -> ()) { hashtagTapHandler = handler } - public func handleURLTap(handler: (NSURL) -> ()) { + public func handleURLTap(_ handler: (URL) -> ()) { urlTapHandler = handler } @@ -67,12 +67,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy customTapHandlers[type] = handler } - public func filterMention(predicate: (String) -> Bool) { + public func filterMention(_ predicate: (String) -> Bool) { mentionFilterPredicate = predicate updateTextStorage() } - public func filterHashtag(predicate: (String) -> Bool) { + public func filterHashtag(_ predicate: (String) -> Bool) { hashtagFilterPredicate = predicate updateTextStorage() } @@ -82,7 +82,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy didSet { updateTextStorage() } } - override public var attributedText: NSAttributedString? { + override public var attributedText: AttributedString? { didSet { updateTextStorage() } } @@ -124,19 +124,20 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy updateTextStorage() } - public override func drawTextInRect(rect: CGRect) { + public override func drawText(in rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) textContainer.size = rect.size let newOrigin = textOrigin(inRect: rect) - layoutManager.drawBackgroundForGlyphRange(range, atPoint: newOrigin) - layoutManager.drawGlyphsForGlyphRange(range, atPoint: newOrigin) + layoutManager.drawBackground(forGlyphRange: range, at: newOrigin) + layoutManager.drawGlyphs(forGlyphRange: range, at: newOrigin) } // MARK: - customzation - public func customize(block: (label: ActiveLabel) -> ()) -> ActiveLabel { + @discardableResult + public func customize(_ block: (label: ActiveLabel) -> ()) -> ActiveLabel { _customizing = true block(label: self) _customizing = false @@ -147,19 +148,19 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - Auto layout public override func intrinsicContentSize() -> CGSize { let superSize = super.intrinsicContentSize() - textContainer.size = CGSize(width: superSize.width, height: CGFloat.max) - let size = layoutManager.usedRectForTextContainer(textContainer) + textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) + let size = layoutManager.usedRect(for: textContainer) return CGSize(width: ceil(size.width), height: ceil(size.height)) } // MARK: - touch events - func onTouch(touch: UITouch) -> Bool { - let location = touch.locationInView(self) + func onTouch(_ touch: UITouch) -> Bool { + let location = touch.location(in: self) var avoidSuperCall = false switch touch.phase { - case .Began, .Moved: - if let element = elementAtLocation(location) { + case .began, .moved: + if let element = element(at: location) { if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length { updateAttributesWhenSelected(false) selectedElement = element @@ -170,26 +171,26 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy updateAttributesWhenSelected(false) selectedElement = nil } - case .Ended: + case .ended: guard let selectedElement = selectedElement else { return avoidSuperCall } switch selectedElement.element { - case .Mention(let userHandle): didTapMention(userHandle) - case .Hashtag(let hashtag): didTapHashtag(hashtag) - case .URL(let url): didTapStringURL(url) - case .Custom(let element): didTap(element, for: selectedElement.type) + case .mention(let userHandle): didTapMention(userHandle) + case .hashtag(let hashtag): didTapHashtag(hashtag) + case .url(let url): didTapStringURL(url) + case .custom(let element): didTap(element, for: selectedElement.type) } - let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))) - dispatch_after(when, dispatch_get_main_queue()) { + let when = DispatchTime.now() + Double(Int64(0.25 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) + DispatchQueue.main.after(when: when) { self.updateAttributesWhenSelected(false) self.selectedElement = nil } avoidSuperCall = true - case .Cancelled: + case .cancelled: updateAttributesWhenSelected(false) selectedElement = nil - case .Stationary: + case .stationary: break } @@ -198,11 +199,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - private properties private var _customizing: Bool = true - private var defaultCustomColor: UIColor = .blackColor() + private var defaultCustomColor: UIColor = .black() private var mentionTapHandler: ((String) -> ())? private var hashtagTapHandler: ((String) -> ())? - private var urlTapHandler: ((NSURL) -> ())? + private var urlTapHandler: ((URL) -> ())? private var customTapHandlers: [ActiveType : ((String) -> ())] = [:] private var mentionFilterPredicate: ((String) -> Bool)? @@ -222,15 +223,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy textContainer.lineFragmentPadding = 0 textContainer.lineBreakMode = lineBreakMode textContainer.maximumNumberOfLines = numberOfLines - userInteractionEnabled = true + isUserInteractionEnabled = true } - private func updateTextStorage(parseText parseText: Bool = true) { + private func updateTextStorage(parseText: Bool = true) { if _customizing { return } // clean up previous active elements - guard let attributedText = attributedText where attributedText.length > 0 else { + guard let attributedText = attributedText, attributedText.length > 0 else { clearActiveElements() - textStorage.setAttributedString(NSAttributedString()) + textStorage.setAttributedString(AttributedString()) setNeedsDisplay() return } @@ -255,16 +256,16 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } private func textOrigin(inRect rect: CGRect) -> CGPoint { - let usedRect = layoutManager.usedRectForTextContainer(textContainer) + let usedRect = layoutManager.usedRect(for: textContainer) heightCorrection = (rect.height - usedRect.height)/2 let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y return CGPoint(x: rect.origin.x, y: glyphOriginY) } /// add link attribute - private func addLinkAttribute(mutAttrString: NSMutableAttributedString) { + private func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { var range = NSRange(location: 0, length: 0) - var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range) + var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) attributes[NSFontAttributeName] = font! attributes[NSForegroundColorAttributeName] = textColor @@ -275,10 +276,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy for (type, elements) in activeElements { switch type { - case .Mention: attributes[NSForegroundColorAttributeName] = mentionColor - case .Hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor - case .URL: attributes[NSForegroundColorAttributeName] = URLColor - case .Custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor + case .mention: attributes[NSForegroundColorAttributeName] = mentionColor + case .hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor + case .url: attributes[NSForegroundColorAttributeName] = URLColor + case .custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } for element in elements { @@ -288,16 +289,16 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } /// use regex check all link ranges - private func parseTextAndExtractActiveElements(attrString: NSAttributedString) { + private func parseTextAndExtractActiveElements(_ attrString: AttributedString) { let textString = attrString.string let textLength = textString.utf16.count let textRange = NSRange(location: 0, length: textLength) for type in enabledTypes { var filter: ((String) -> Bool)? = nil - if type == .Mention { + if type == .mention { filter = mentionFilterPredicate - } else if type == .Hashtag { + } else if type == .hashtag { filter = hashtagFilterPredicate } let hashtagElements = ActiveBuilder.createElements(type, from: textString, range: textRange, filterPredicate: filter) @@ -307,14 +308,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy /// add line break mode - private func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString { + private func addLineBreak(_ attrString: AttributedString) -> NSMutableAttributedString { let mutAttrString = NSMutableAttributedString(attributedString: attrString) var range = NSRange(location: 0, length: 0) - var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range) + var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() - paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping + paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = CGFloat(lineSpacing) @@ -324,21 +325,21 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy return mutAttrString } - private func updateAttributesWhenSelected(isSelected: Bool) { + private func updateAttributesWhenSelected(_ isSelected: Bool) { guard let selectedElement = selectedElement else { return } - var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil) + var attributes = textStorage.attributes(at: 0, effectiveRange: nil) let type = selectedElement.type if isSelected { let selectedColor: UIColor switch type { - case .Mention: selectedColor = mentionSelectedColor ?? mentionColor - case .Hashtag: selectedColor = hashtagSelectedColor ?? hashtagColor - case .URL: selectedColor = URLSelectedColor ?? URLColor - case .Custom: + case .mention: selectedColor = mentionSelectedColor ?? mentionColor + case .hashtag: selectedColor = hashtagSelectedColor ?? hashtagColor + case .url: selectedColor = URLSelectedColor ?? URLColor + case .custom: let possibleSelectedColor = customSelectedColor[selectedElement.type] ?? customColor[selectedElement.type] selectedColor = possibleSelectedColor ?? defaultCustomColor } @@ -346,10 +347,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } else { let unselectedColor: UIColor switch type { - case .Mention: unselectedColor = mentionColor - case .Hashtag: unselectedColor = hashtagColor - case .URL: unselectedColor = URLColor - case .Custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor + case .mention: unselectedColor = mentionColor + case .hashtag: unselectedColor = hashtagColor + case .url: unselectedColor = URLColor + case .custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor } attributes[NSForegroundColorAttributeName] = unselectedColor } @@ -359,19 +360,19 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setNeedsDisplay() } - private func elementAtLocation(location: CGPoint) -> ElementTuple? { + private func element(at location: CGPoint) -> ElementTuple? { guard textStorage.length > 0 else { return nil } var correctLocation = location correctLocation.y -= heightCorrection - let boundingRect = layoutManager.boundingRectForGlyphRange(NSRange(location: 0, length: textStorage.length), inTextContainer: textContainer) + let boundingRect = layoutManager.boundingRect(forGlyphRange: NSRange(location: 0, length: textStorage.length), in: textContainer) guard boundingRect.contains(correctLocation) else { return nil } - let index = layoutManager.glyphIndexForPoint(correctLocation, inTextContainer: textContainer) + let index = layoutManager.glyphIndex(for: correctLocation, in: textContainer) for element in activeElements.map({ $0.1 }).flatten() { if index >= element.range.location && index <= element.range.location + element.range.length { @@ -384,58 +385,58 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy //MARK: - Handle UI Responder touches - public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } - super.touchesBegan(touches, withEvent: event) + super.touchesBegan(touches, with: event) } - public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } - super.touchesMoved(touches, withEvent: event) + super.touchesMoved(touches, with: event) } - public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { - guard let touch = touches?.first else { return } - onTouch(touch) - super.touchesCancelled(touches, withEvent: event) + public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + _ = onTouch(touch) + super.touchesCancelled(touches, with: event) } - public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } - super.touchesEnded(touches, withEvent: event) + super.touchesEnded(touches, with: event) } //MARK: - ActiveLabel handler - private func didTapMention(username: String) { + private func didTapMention(_ username: String) { guard let mentionHandler = mentionTapHandler else { - delegate?.didSelectText(username, type: .Mention) + delegate?.didSelect(username, type: .mention) return } mentionHandler(username) } - private func didTapHashtag(hashtag: String) { + private func didTapHashtag(_ hashtag: String) { guard let hashtagHandler = hashtagTapHandler else { - delegate?.didSelectText(hashtag, type: .Hashtag) + delegate?.didSelect(hashtag, type: .hashtag) return } hashtagHandler(hashtag) } - private func didTapStringURL(stringURL: String) { - guard let urlHandler = urlTapHandler, let url = NSURL(string: stringURL) else { - delegate?.didSelectText(stringURL, type: .URL) + private func didTapStringURL(_ stringURL: String) { + guard let urlHandler = urlTapHandler, let url = URL(string: stringURL) else { + delegate?.didSelect(stringURL, type: .url) return } urlHandler(url) } - private func didTap(element: String, for type: ActiveType) { + private func didTap(_ element: String, for type: ActiveType) { guard let elementHandler = customTapHandlers[type] else { - delegate?.didSelectText(element, type: type) + delegate?.didSelect(element, type: type) return } elementHandler(element) @@ -444,15 +445,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy extension ActiveLabel: UIGestureRecognizerDelegate { - public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } } diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 9d26366a..b01cc700 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -9,33 +9,33 @@ import Foundation enum ActiveElement { - case Mention(String) - case Hashtag(String) - case URL(String) - case Custom(String) + case mention(String) + case hashtag(String) + case url(String) + case custom(String) static func create(with activeType: ActiveType, text: String) -> ActiveElement { switch activeType { - case .Mention: return Mention(text) - case .Hashtag: return Hashtag(text) - case .URL: return URL(text) - case .Custom: return Custom(text) + case .mention: return mention(text) + case .hashtag: return hashtag(text) + case .url: return url(text) + case .custom: return custom(text) } } } public enum ActiveType { - case Mention - case Hashtag - case URL - case Custom(pattern: String) + case mention + case hashtag + case url + case custom(pattern: String) var pattern: String { switch self { - case .Mention: return RegexParser.mentionPattern - case .Hashtag: return RegexParser.hashtagPattern - case .URL: return RegexParser.urlPattern - case .Custom(let regex): return regex + case .mention: return RegexParser.mentionPattern + case .hashtag: return RegexParser.hashtagPattern + case .url: return RegexParser.urlPattern + case .custom(let regex): return regex } } } @@ -43,33 +43,33 @@ public enum ActiveType { extension ActiveType: Hashable, Equatable { public var hashValue: Int { switch self { - case .Mention: return -1 - case .Hashtag: return -2 - case .URL: return -3 - case .Custom(let regex): return regex.hashValue + case .mention: return -1 + case .hashtag: return -2 + case .url: return -3 + case .custom(let regex): return regex.hashValue } } } public func ==(lhs: ActiveType, rhs: ActiveType) -> Bool { switch (lhs, rhs) { - case (.Mention, .Mention): return true - case (.Hashtag, .Hashtag): return true - case (.URL, .URL): return true - case (.Custom(let pattern1), .Custom(let pattern2)): return pattern1 == pattern2 + case (.mention, .mention): return true + case (.hashtag, .hashtag): return true + case (.url, .url): return true + case (.custom(let pattern1), .custom(let pattern2)): return pattern1 == pattern2 default: return false } } -typealias ActiveFilterPredicate = (String -> Bool) +typealias ActiveFilterPredicate = ((String) -> Bool) struct ActiveBuilder { - static func createElements(type: ActiveType, from text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + static func createElements(_ type: ActiveType, from text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { switch type { - case .Mention, .Hashtag: + case .mention, .hashtag: return createElementsIgnoringFirstCharacter(from: text, for: type, range: range, filterPredicate: filterPredicate) - case .URL, .Custom: + case .url, .custom: return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) } } @@ -83,8 +83,8 @@ struct ActiveBuilder { var elements: [ElementTuple] = [] for match in matches where match.range.length > 2 { - let word = nsstring.substringWithRange(match.range) - .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + let word = nsstring.substring(with: match.range) + .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) if filterPredicate?(word) ?? true { let element = ActiveElement.create(with: type, text: word) elements.append((match.range, element, type)) @@ -103,12 +103,12 @@ struct ActiveBuilder { for match in matches where match.range.length > 2 { let range = NSRange(location: match.range.location + 1, length: match.range.length - 1) - var word = nsstring.substringWithRange(range) + var word = nsstring.substring(with: range) if word.hasPrefix("@") { - word.removeAtIndex(word.startIndex) + word.remove(at: word.startIndex) } else if word.hasPrefix("#") { - word.removeAtIndex(word.startIndex) + word.remove(at: word.startIndex) } if filterPredicate?(word) ?? true { diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 809badcc..cdb03516 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -17,8 +17,8 @@ struct RegexParser { "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" - static func getElements(from text: String, with pattern: String, range: NSRange) -> [NSTextCheckingResult]{ - guard let elementRegex = try? NSRegularExpression(pattern: pattern, options: [.CaseInsensitive]) else { return [] } - return elementRegex.matchesInString(text, options: [], range: range) + static func getElements(from text: String, with pattern: String, range: NSRange) -> [TextCheckingResult]{ + guard let elementRegex = try? RegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] } + return elementRegex.matches(in: text, options: [], range: range) } -} \ No newline at end of file +} diff --git a/ActiveLabelDemo/AppDelegate.swift b/ActiveLabelDemo/AppDelegate.swift index b8aecfc9..ba5aa20d 100644 --- a/ActiveLabelDemo/AppDelegate.swift +++ b/ActiveLabelDemo/AppDelegate.swift @@ -14,30 +14,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. return true } - func applicationWillResignActive(application: UIApplication) { + func applicationWillResignActive(_ application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - func applicationDidEnterBackground(application: UIApplication) { + func applicationDidEnterBackground(_ application: UIApplication) { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - func applicationWillEnterForeground(application: UIApplication) { + func applicationWillEnterForeground(_ application: UIApplication) { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - func applicationDidBecomeActive(application: UIApplication) { + func applicationDidBecomeActive(_ application: UIApplication) { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - func applicationWillTerminate(application: UIApplication) { + func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index bb864cc5..1f4e5d92 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -16,8 +16,8 @@ class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - let customType = ActiveType.Custom(pattern: "\\sare\\b") //Looks for "are" - let customType2 = ActiveType.Custom(pattern: "\\sit\\b") //Looks for "it" + let customType = ActiveType.custom(pattern: "\\sare\\b") //Looks for "are" + let customType2 = ActiveType.custom(pattern: "\\sit\\b") //Looks for "it" label.enabledTypes.append(customType) label.enabledTypes.append(customType2) @@ -36,14 +36,14 @@ class ViewController: UIViewController { label.handleMentionTap { self.alert("Mention", message: $0) } label.handleHashtagTap { self.alert("Hashtag", message: $0) } - label.handleURLTap { self.alert("URL", message: $0.absoluteString) } + label.handleURLTap { self.alert("URL", message: $0.absoluteString!) } //Custom types - label.customColor[customType] = UIColor.purpleColor() - label.customSelectedColor[customType] = UIColor.greenColor() - label.customColor[customType2] = UIColor.magentaColor() - label.customSelectedColor[customType2] = UIColor.greenColor() + label.customColor[customType] = UIColor.purple() + label.customSelectedColor[customType] = UIColor.green() + label.customColor[customType2] = UIColor.magenta() + label.customSelectedColor[customType2] = UIColor.green() label.handleCustomTap(for: customType) { self.alert("Custom type", message: $0) } label.handleCustomTap(for: customType2) { self.alert("Custom type", message: $0) } @@ -61,10 +61,10 @@ class ViewController: UIViewController { // Dispose of any resources that can be recreated. } - func alert(title: String, message: String) { - let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert) - vc.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil)) - presentViewController(vc, animated: true, completion: nil) + func alert(_ title: String, message: String) { + let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) + vc.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil)) + present(vc, animated: true, completion: nil) } } From 2a11c9f137653eea90f5ab0131a66e7ae8a4b235 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 29 Jul 2016 22:18:18 +0200 Subject: [PATCH 032/116] Update travis to use Xcode 8 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 330e43fb..9a17e95f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,5 @@ language: objective-c xcode_project: ActiveLabel.xcodeproj xcode_scheme: ActiveLabel -osx_image: xcode7.3 -xcode_sdk: iphonesimulator9.3 +osx_image: xcode8 +xcode_sdk: iphonesimulator10.0 From 7c46c130167721d5bc205064738b81c7db4b4d9f Mon Sep 17 00:00:00 2001 From: bAmpT Date: Sat, 20 Aug 2016 14:55:24 +0200 Subject: [PATCH 033/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index e988d139..ee021389 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.6.0' + s.version = '0.6.1' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From fc7e17218b27bdf0cb98751136d6e0fbe937ceb0 Mon Sep 17 00:00:00 2001 From: bAmpT Date: Sat, 20 Aug 2016 15:01:27 +0200 Subject: [PATCH 034/116] Update ActiveType.swift Removed the 3 glyph limit and trimming of whitespace/newline for Custom Type. --- ActiveLabel/ActiveType.swift | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 9d26366a..874c2189 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -69,8 +69,10 @@ struct ActiveBuilder { switch type { case .Mention, .Hashtag: return createElementsIgnoringFirstCharacter(from: text, for: type, range: range, filterPredicate: filterPredicate) - case .URL, .Custom: + case .URL: return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) + case .Custom: + return createElementsCustom(from: text, for: type, range: range, filterPredicate: filterPredicate) } } @@ -92,6 +94,24 @@ struct ActiveBuilder { } return elements } + + private static func createElementsCustom(from text: String, + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) + let nsstring = text as NSString + var elements: [ElementTuple] = [] + + for match in matches { + let word = nsstring.substringWithRange(match.range) + if filterPredicate?(word) ?? true { + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) + } + } + return elements + } private static func createElementsIgnoringFirstCharacter(from text: String, for type: ActiveType, From 1765a8194beaf9a5ffb348d0cf613a966b446c62 Mon Sep 17 00:00:00 2001 From: bAmpT Date: Sat, 20 Aug 2016 15:18:03 +0200 Subject: [PATCH 035/116] Update ActiveType.swift Added the whitespace/newline trimming again. --- ActiveLabel/ActiveType.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 874c2189..d6861fb4 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -105,6 +105,7 @@ struct ActiveBuilder { for match in matches { let word = nsstring.substringWithRange(match.range) + .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) if filterPredicate?(word) ?? true { let element = ActiveElement.create(with: type, text: word) elements.append((match.range, element, type)) From 4c578db864f903409098f75669b6988e80b51e05 Mon Sep 17 00:00:00 2001 From: bAmpT Date: Sat, 20 Aug 2016 21:05:27 +0200 Subject: [PATCH 036/116] Update ActiveType.swift --- ActiveLabel/ActiveType.swift | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index d6861fb4..446cbb87 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -72,38 +72,20 @@ struct ActiveBuilder { case .URL: return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) case .Custom: - return createElementsCustom(from: text, for: type, range: range, filterPredicate: filterPredicate) + return createElements(from: text, for: type, range: range, minLength:0, filterPredicate: filterPredicate) } } private static func createElements(from text: String, for type: ActiveType, range: NSRange, + minLength: Int = 2, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) let nsstring = text as NSString var elements: [ElementTuple] = [] - for match in matches where match.range.length > 2 { - let word = nsstring.substringWithRange(match.range) - .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - if filterPredicate?(word) ?? true { - let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) - } - } - return elements - } - - private static func createElementsCustom(from text: String, - for type: ActiveType, - range: NSRange, - filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { - let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) - let nsstring = text as NSString - var elements: [ElementTuple] = [] - - for match in matches { + for match in matches where match.range.length > minLength { let word = nsstring.substringWithRange(match.range) .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) if filterPredicate?(word) ?? true { From f6dbed0a91ae01fe13d4b42e5c04195031028585 Mon Sep 17 00:00:00 2001 From: Marvin Nazari Date: Wed, 24 Aug 2016 12:18:28 +0200 Subject: [PATCH 037/116] Updated to latest Swift and Xcode 8 (beta 6) --- ActiveLabel.xcodeproj/project.pbxproj | 12 +- .../xcschemes/ActiveLabel.xcscheme | 2 +- ActiveLabel/ActiveLabel.swift | 154 +++++++++--------- ActiveLabel/ActiveType.swift | 4 +- ActiveLabel/RegexParser.swift | 4 +- ActiveLabelDemo/AppDelegate.swift | 5 +- ActiveLabelDemo/ViewController.swift | 10 +- ActiveLabelTests/ActiveTypeTests.swift | 88 +++++----- 8 files changed, 144 insertions(+), 135 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index ce2eea3f..281c91ff 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -203,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = Optonaut; TargetAttributes = { 8F0249A11B9989B1005D8035 = { @@ -212,6 +212,7 @@ }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0800; }; 8F0249BF1B998A66005D8035 = { CreatedOnToolsVersion = 7.0; @@ -336,8 +337,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -384,8 +387,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -405,6 +410,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -416,6 +422,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -436,6 +443,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -458,6 +466,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -468,6 +477,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme index b9944f43..b5d72fd3 100644 --- a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme +++ b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme @@ -1,6 +1,6 @@ ()) { + open func handleMentionTap(_ handler: @escaping (String) -> ()) { mentionTapHandler = handler } - public func handleHashtagTap(_ handler: (String) -> ()) { + open func handleHashtagTap(_ handler: @escaping (String) -> ()) { hashtagTapHandler = handler } - public func handleURLTap(_ handler: (URL) -> ()) { + open func handleURLTap(_ handler: @escaping (URL) -> ()) { urlTapHandler = handler } - public func handleCustomTap(for type: ActiveType, handler: (String) -> ()) { + open func handleCustomTap(for type: ActiveType, handler: @escaping (String) -> ()) { customTapHandlers[type] = handler } - public func filterMention(_ predicate: (String) -> Bool) { + open func filterMention(_ predicate: @escaping (String) -> Bool) { mentionFilterPredicate = predicate updateTextStorage() } - public func filterHashtag(_ predicate: (String) -> Bool) { + open func filterHashtag(_ predicate: @escaping (String) -> Bool) { hashtagFilterPredicate = predicate updateTextStorage() } // MARK: - override UILabel properties - override public var text: String? { + override open var text: String? { didSet { updateTextStorage() } } - override public var attributedText: AttributedString? { + override open var attributedText: NSAttributedString? { didSet { updateTextStorage() } } - override public var font: UIFont! { - didSet { updateTextStorage(parseText: false) } + override open var font: UIFont! { + didSet { updateTextStorage(false) } } - override public var textColor: UIColor! { - didSet { updateTextStorage(parseText: false) } + override open var textColor: UIColor! { + didSet { updateTextStorage(false) } } - override public var textAlignment: NSTextAlignment { - didSet { updateTextStorage(parseText: false)} + override open var textAlignment: NSTextAlignment { + didSet { updateTextStorage(false)} } - public override var numberOfLines: Int { + open override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } - public override var lineBreakMode: NSLineBreakMode { + open override var lineBreakMode: NSLineBreakMode { didSet { textContainer.lineBreakMode = lineBreakMode } } @@ -119,12 +119,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setupLabel() } - public override func awakeFromNib() { + open override func awakeFromNib() { super.awakeFromNib() updateTextStorage() } - public override func drawText(in rect: CGRect) { + open override func drawText(in rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) textContainer.size = rect.size @@ -137,22 +137,22 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - customzation @discardableResult - public func customize(_ block: (label: ActiveLabel) -> ()) -> ActiveLabel { + open func customize(_ block: (_ label: ActiveLabel) -> ()) -> ActiveLabel { _customizing = true - block(label: self) + block(self) _customizing = false updateTextStorage() return self } // MARK: - Auto layout - public override func intrinsicContentSize() -> CGSize { - let superSize = super.intrinsicContentSize() + open override var intrinsicContentSize: CGSize { + let superSize = super.intrinsicContentSize textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) let size = layoutManager.usedRect(for: textContainer) return CGSize(width: ceil(size.width), height: ceil(size.height)) } - + // MARK: - touch events func onTouch(_ touch: UITouch) -> Bool { let location = touch.location(in: self) @@ -182,7 +182,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } let when = DispatchTime.now() + Double(Int64(0.25 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) - DispatchQueue.main.after(when: when) { + DispatchQueue.main.asyncAfter(deadline: when) { self.updateAttributesWhenSelected(false) self.selectedElement = nil } @@ -198,26 +198,26 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } // MARK: - private properties - private var _customizing: Bool = true - private var defaultCustomColor: UIColor = .black() + fileprivate var _customizing: Bool = true + fileprivate var defaultCustomColor: UIColor = .black - private var mentionTapHandler: ((String) -> ())? - private var hashtagTapHandler: ((String) -> ())? - private var urlTapHandler: ((URL) -> ())? - private var customTapHandlers: [ActiveType : ((String) -> ())] = [:] + fileprivate var mentionTapHandler: ((String) -> ())? + fileprivate var hashtagTapHandler: ((String) -> ())? + fileprivate var urlTapHandler: ((URL) -> ())? + fileprivate var customTapHandlers: [ActiveType : ((String) -> ())] = [:] - private var mentionFilterPredicate: ((String) -> Bool)? - private var hashtagFilterPredicate: ((String) -> Bool)? + fileprivate var mentionFilterPredicate: ((String) -> Bool)? + fileprivate var hashtagFilterPredicate: ((String) -> Bool)? - private var selectedElement: ElementTuple? - private var heightCorrection: CGFloat = 0 - private lazy var textStorage = NSTextStorage() - private lazy var layoutManager = NSLayoutManager() - private lazy var textContainer = NSTextContainer() + fileprivate var selectedElement: ElementTuple? + fileprivate var heightCorrection: CGFloat = 0 + fileprivate lazy var textStorage = NSTextStorage() + fileprivate lazy var layoutManager = NSLayoutManager() + fileprivate lazy var textContainer = NSTextContainer() lazy var activeElements = [ActiveType: [ElementTuple]]() // MARK: - helper functions - private func setupLabel() { + fileprivate func setupLabel() { textStorage.addLayoutManager(layoutManager) layoutManager.addTextContainer(textContainer) textContainer.lineFragmentPadding = 0 @@ -226,12 +226,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy isUserInteractionEnabled = true } - private func updateTextStorage(parseText: Bool = true) { + fileprivate func updateTextStorage(_ parseText: Bool = true) { if _customizing { return } // clean up previous active elements guard let attributedText = attributedText, attributedText.length > 0 else { clearActiveElements() - textStorage.setAttributedString(AttributedString()) + textStorage.setAttributedString(NSAttributedString()) setNeedsDisplay() return } @@ -248,14 +248,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setNeedsDisplay() } - private func clearActiveElements() { + fileprivate func clearActiveElements() { selectedElement = nil for (type, _) in activeElements { activeElements[type]?.removeAll() } } - private func textOrigin(inRect rect: CGRect) -> CGPoint { + fileprivate func textOrigin(inRect rect: CGRect) -> CGPoint { let usedRect = layoutManager.usedRect(for: textContainer) heightCorrection = (rect.height - usedRect.height)/2 let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y @@ -263,7 +263,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } /// add link attribute - private func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { + fileprivate func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) @@ -289,7 +289,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } /// use regex check all link ranges - private func parseTextAndExtractActiveElements(_ attrString: AttributedString) { + fileprivate func parseTextAndExtractActiveElements(_ attrString: NSAttributedString) { let textString = attrString.string let textLength = textString.utf16.count let textRange = NSRange(location: 0, length: textLength) @@ -308,7 +308,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy /// add line break mode - private func addLineBreak(_ attrString: AttributedString) -> NSMutableAttributedString { + fileprivate func addLineBreak(_ attrString: NSAttributedString) -> NSMutableAttributedString { let mutAttrString = NSMutableAttributedString(attributedString: attrString) var range = NSRange(location: 0, length: 0) @@ -325,7 +325,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy return mutAttrString } - private func updateAttributesWhenSelected(_ isSelected: Bool) { + fileprivate func updateAttributesWhenSelected(_ isSelected: Bool) { guard let selectedElement = selectedElement else { return } @@ -360,7 +360,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setNeedsDisplay() } - private func element(at location: CGPoint) -> ElementTuple? { + fileprivate func element(at location: CGPoint) -> ElementTuple? { guard textStorage.length > 0 else { return nil } @@ -374,7 +374,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let index = layoutManager.glyphIndex(for: correctLocation, in: textContainer) - for element in activeElements.map({ $0.1 }).flatten() { + for element in activeElements.map({ $0.1 }).joined() { if index >= element.range.location && index <= element.range.location + element.range.length { return element } @@ -385,32 +385,32 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy //MARK: - Handle UI Responder touches - public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesBegan(touches, with: event) } - public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + open override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesMoved(touches, with: event) } - public override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } _ = onTouch(touch) super.touchesCancelled(touches, with: event) } - public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesEnded(touches, with: event) } //MARK: - ActiveLabel handler - private func didTapMention(_ username: String) { + fileprivate func didTapMention(_ username: String) { guard let mentionHandler = mentionTapHandler else { delegate?.didSelect(username, type: .mention) return @@ -418,7 +418,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy mentionHandler(username) } - private func didTapHashtag(_ hashtag: String) { + fileprivate func didTapHashtag(_ hashtag: String) { guard let hashtagHandler = hashtagTapHandler else { delegate?.didSelect(hashtag, type: .hashtag) return @@ -426,7 +426,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy hashtagHandler(hashtag) } - private func didTapStringURL(_ stringURL: String) { + fileprivate func didTapStringURL(_ stringURL: String) { guard let urlHandler = urlTapHandler, let url = URL(string: stringURL) else { delegate?.didSelect(stringURL, type: .url) return @@ -434,7 +434,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy urlHandler(url) } - private func didTap(_ element: String, for type: ActiveType) { + fileprivate func didTap(_ element: String, for type: ActiveType) { guard let elementHandler = customTapHandlers[type] else { delegate?.didSelect(element, type: type) return diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index b01cc700..d9fb1c56 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -74,7 +74,7 @@ struct ActiveBuilder { } } - private static func createElements(from text: String, + fileprivate static func createElements(from text: String, for type: ActiveType, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { @@ -93,7 +93,7 @@ struct ActiveBuilder { return elements } - private static func createElementsIgnoringFirstCharacter(from text: String, + fileprivate static func createElementsIgnoringFirstCharacter(from text: String, for type: ActiveType, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index cdb03516..998dc0db 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -17,8 +17,8 @@ struct RegexParser { "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" - static func getElements(from text: String, with pattern: String, range: NSRange) -> [TextCheckingResult]{ - guard let elementRegex = try? RegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] } + static func getElements(from text: String, with pattern: String, range: NSRange) -> [NSTextCheckingResult]{ + guard let elementRegex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] } return elementRegex.matches(in: text, options: [], range: range) } } diff --git a/ActiveLabelDemo/AppDelegate.swift b/ActiveLabelDemo/AppDelegate.swift index ba5aa20d..73d7eb2e 100644 --- a/ActiveLabelDemo/AppDelegate.swift +++ b/ActiveLabelDemo/AppDelegate.swift @@ -13,9 +13,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - // Override point for customization after application launch. + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { + return true } diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index 1f4e5d92..ea398d56 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -36,14 +36,14 @@ class ViewController: UIViewController { label.handleMentionTap { self.alert("Mention", message: $0) } label.handleHashtagTap { self.alert("Hashtag", message: $0) } - label.handleURLTap { self.alert("URL", message: $0.absoluteString!) } + label.handleURLTap { self.alert("URL", message: $0.absoluteString) } //Custom types - label.customColor[customType] = UIColor.purple() - label.customSelectedColor[customType] = UIColor.green() - label.customColor[customType2] = UIColor.magenta() - label.customSelectedColor[customType2] = UIColor.green() + label.customColor[customType] = UIColor.purple + label.customSelectedColor[customType] = UIColor.green + label.customColor[customType2] = UIColor.magenta + label.customSelectedColor[customType2] = UIColor.green label.handleCustomTap(for: customType) { self.alert("Custom type", message: $0) } label.handleCustomTap(for: customType2) { self.alert("Custom type", message: $0) } diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 4f68ea7e..2ef5c425 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -13,10 +13,10 @@ extension ActiveElement: Equatable {} func ==(a: ActiveElement, b: ActiveElement) -> Bool { switch (a, b) { - case (.Mention(let a), .Mention(let b)) where a == b: return true - case (.Hashtag(let a), .Hashtag(let b)) where a == b: return true - case (.URL(let a), .URL(let b)) where a == b: return true - case (.Custom(let a), .Custom(let b)) where a == b: return true + case (.mention(let a), .mention(let b)) where a == b: return true + case (.hashtag(let a), .hashtag(let b)) where a == b: return true + case (.url(let a), .url(let b)) where a == b: return true + case (.custom(let a), .custom(let b)) where a == b: return true default: return false } } @@ -24,7 +24,7 @@ func ==(a: ActiveElement, b: ActiveElement) -> Bool { class ActiveTypeTests: XCTestCase { let label = ActiveLabel() - let customEmptyType = ActiveType.Custom(pattern: "") + let customEmptyType = ActiveType.custom(pattern: "") var activeElements: [ActiveElement] { return label.activeElements.flatMap({$0.1.flatMap({$0.element})}) @@ -33,26 +33,26 @@ class ActiveTypeTests: XCTestCase { var currentElementString: String? { guard let currentElement = activeElements.first else { return nil } switch currentElement { - case .Mention(let mention): return mention - case .Hashtag(let hashtag): return hashtag - case .URL(let url): return url - case .Custom(let element): return element + case .mention(let mention): return mention + case .hashtag(let hashtag): return hashtag + case .url(let url): return url + case .custom(let element): return element } } var currentElementType: ActiveType? { guard let currentElement = activeElements.first else { return nil } switch currentElement { - case .Mention: return .Mention - case .Hashtag: return .Hashtag - case .URL: return .URL - case .Custom: return customEmptyType + case .mention: return .mention + case .hashtag: return .hashtag + case .url: return .url + case .custom: return customEmptyType } } override func setUp() { super.setUp() - label.enabledTypes = [.Mention, .Hashtag, .URL] + label.enabledTypes = [.mention, .hashtag, .url] // Put setup code here. This method is called before the invocation of each test method in the class. } @@ -78,42 +78,42 @@ class ActiveTypeTests: XCTestCase { label.text = "@userhandle" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "userhandle") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "@userhandle." XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "userhandle") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "@_with_underscores_" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "_with_underscores_") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = " . @userhandle" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "userhandle") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "@user#hashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "user") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "@user@mention" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "user") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = ".@userhandle" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "userhandle") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = " .@userhandle" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "userhandle") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "word@mention" XCTAssertEqual(activeElements.count, 0) @@ -129,32 +129,32 @@ class ActiveTypeTests: XCTestCase { label.text = "#somehashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "somehashtag") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = "#somehashtag." XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "somehashtag") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = "#_with_underscores_" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "_with_underscores_") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = " . #somehashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "somehashtag") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = "#some#hashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "some") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = "#some@mention" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "some") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = ".#somehashtag" XCTAssertEqual(activeElements.count, 0) @@ -174,34 +174,34 @@ class ActiveTypeTests: XCTestCase { label.text = "http://www.google.com" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "http://www.google.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "https://www.google.com" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "https://www.google.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "http://www.google.com." XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "http://www.google.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "www.google.com" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "www.google.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "pic.twitter.com/YUGdEbUx" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "pic.twitter.com/YUGdEbUx") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "google.com" XCTAssertEqual(activeElements.count, 0) } func testCustomType() { - let newType = ActiveType.Custom(pattern: "\\sare\\b") + let newType = ActiveType.custom(pattern: "\\sare\\b") label.enabledTypes.append(newType) label.text = "we are one" @@ -247,12 +247,12 @@ class ActiveTypeTests: XCTestCase { } func testOnlyMentionsEnabled() { - label.enabledTypes = [.Mention] + label.enabledTypes = [.mention] label.text = "@user #hashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "user") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) label.text = "http://www.google.com" XCTAssertEqual(activeElements.count, 0) @@ -263,16 +263,16 @@ class ActiveTypeTests: XCTestCase { label.text = "@userNumberOne #hashtag http://www.google.com @anotheruser" XCTAssertEqual(activeElements.count, 2) XCTAssertEqual(currentElementString, "userNumberOne") - XCTAssertEqual(currentElementType, ActiveType.Mention) + XCTAssertEqual(currentElementType, ActiveType.mention) } func testOnlyHashtagEnabled() { - label.enabledTypes = [.Hashtag] + label.enabledTypes = [.hashtag] label.text = "@user #hashtag" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "hashtag") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) label.text = "http://www.google.com" XCTAssertEqual(activeElements.count, 0) @@ -283,16 +283,16 @@ class ActiveTypeTests: XCTestCase { label.text = "#hashtagNumberOne #hashtag http://www.google.com @anotheruser" XCTAssertEqual(activeElements.count, 2) XCTAssertEqual(currentElementString, "hashtagNumberOne") - XCTAssertEqual(currentElementType, ActiveType.Hashtag) + XCTAssertEqual(currentElementType, ActiveType.hashtag) } func testOnlyURLsEnabled() { - label.enabledTypes = [.URL] + label.enabledTypes = [.url] label.text = "http://www.google.com #hello" XCTAssertEqual(activeElements.count, 1) XCTAssertEqual(currentElementString, "http://www.google.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) label.text = "@user" XCTAssertEqual(activeElements.count, 0) @@ -303,11 +303,11 @@ class ActiveTypeTests: XCTestCase { label.text = " http://www.apple.com @userNumberOne #hashtag http://www.google.com @anotheruser" XCTAssertEqual(activeElements.count, 2) XCTAssertEqual(currentElementString, "http://www.apple.com") - XCTAssertEqual(currentElementType, ActiveType.URL) + XCTAssertEqual(currentElementType, ActiveType.url) } func testOnlyCustomEnabled() { - let newType = ActiveType.Custom(pattern: "\\sare\\b") + let newType = ActiveType.custom(pattern: "\\sare\\b") label.enabledTypes = [newType] label.text = "http://www.google.com are #hello" From c590f413d8c10caddb494dd1daae6609b58ce861 Mon Sep 17 00:00:00 2001 From: bAmpT Date: Thu, 25 Aug 2016 13:28:15 +0200 Subject: [PATCH 038/116] Update ActiveType.swift --- ActiveLabel/ActiveType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index 446cbb87..d2393267 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -72,7 +72,7 @@ struct ActiveBuilder { case .URL: return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) case .Custom: - return createElements(from: text, for: type, range: range, minLength:0, filterPredicate: filterPredicate) + return createElements(from: text, for: type, range: range, minLength: 1, filterPredicate: filterPredicate) } } From 7d5e89bd559e2dc85129e364c00ee87ed88926a7 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Thu, 25 Aug 2016 19:26:41 +0200 Subject: [PATCH 039/116] Update for Xc8b6 --- ActiveLabel/ActiveLabel.swift | 27 ++++++++++++++------------- ActiveLabel/ActiveType.swift | 12 ++++++------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index b47e7aa1..814684f7 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -23,31 +23,31 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy open var enabledTypes: [ActiveType] = [.mention, .hashtag, .url] @IBInspectable open var mentionColor: UIColor = .blue { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var mentionSelectedColor: UIColor? { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var hashtagColor: UIColor = .blue { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var hashtagSelectedColor: UIColor? { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var URLColor: UIColor = .blue { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var URLSelectedColor: UIColor? { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } open var customColor: [ActiveType : UIColor] = [:] { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } open var customSelectedColor: [ActiveType : UIColor] = [:] { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } @IBInspectable open var lineSpacing: Float = 0 { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } // MARK: - public methods @@ -87,15 +87,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } override open var font: UIFont! { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } override open var textColor: UIColor! { - didSet { updateTextStorage(false) } + didSet { updateTextStorage(parseText: false) } } override open var textAlignment: NSTextAlignment { - didSet { updateTextStorage(false)} + didSet { updateTextStorage(parseText: false)} } open override var numberOfLines: Int { @@ -146,6 +146,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } // MARK: - Auto layout + open override var intrinsicContentSize: CGSize { let superSize = super.intrinsicContentSize textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) @@ -226,7 +227,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy isUserInteractionEnabled = true } - fileprivate func updateTextStorage(_ parseText: Bool = true) { + fileprivate func updateTextStorage(parseText: Bool = true) { if _customizing { return } // clean up previous active elements guard let attributedText = attributedText, attributedText.length > 0 else { diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index d9fb1c56..1a2d615a 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -75,9 +75,9 @@ struct ActiveBuilder { } fileprivate static func createElements(from text: String, - for type: ActiveType, - range: NSRange, - filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) let nsstring = text as NSString var elements: [ElementTuple] = [] @@ -94,9 +94,9 @@ struct ActiveBuilder { } fileprivate static func createElementsIgnoringFirstCharacter(from text: String, - for type: ActiveType, - range: NSRange, - filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) let nsstring = text as NSString var elements: [ElementTuple] = [] From 85dcbdc71ca3b425bf50a8f3b27204805a5d3243 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 20:15:51 +0200 Subject: [PATCH 040/116] Add ability to trim long URLs --- ActiveLabel.xcodeproj/project.pbxproj | 10 +- ActiveLabel/ActiveBuilder.swift | 99 ++++++++++++++++++ ActiveLabel/ActiveLabel.swift | 141 ++++++++++++++------------ ActiveLabel/ActiveType.swift | 67 +----------- ActiveLabel/StringTrimExtension.swift | 16 +++ ActiveLabelDemo/ViewController.swift | 2 + 6 files changed, 206 insertions(+), 129 deletions(-) create mode 100644 ActiveLabel/ActiveBuilder.swift create mode 100644 ActiveLabel/StringTrimExtension.swift diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 88a4c04b..598a5f66 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 8F0249CD1B998A66005D8035 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8F0249CB1B998A66005D8035 /* LaunchScreen.storyboard */; }; 8F0249D31B998C00005D8035 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0249D21B998C00005D8035 /* ActiveLabel.swift */; }; 8F0249D51B998D21005D8035 /* ActiveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F0249D41B998D21005D8035 /* ActiveType.swift */; }; + C1D15C791D7C9B610041D119 /* StringTrimExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D15C781D7C9B610041D119 /* StringTrimExtension.swift */; }; + C1D15C7B1D7C9B7E0041D119 /* ActiveBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D15C7A1D7C9B7E0041D119 /* ActiveBuilder.swift */; }; C1E867D61C3D7AEA00FD687A /* RegexParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E867D51C3D7AEA00FD687A /* RegexParser.swift */; }; /* End PBXBuildFile section */ @@ -46,6 +48,8 @@ 8F0249CE1B998A66005D8035 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8F0249D21B998C00005D8035 /* ActiveLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveLabel.swift; sourceTree = ""; }; 8F0249D41B998D21005D8035 /* ActiveType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveType.swift; sourceTree = ""; }; + C1D15C781D7C9B610041D119 /* StringTrimExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringTrimExtension.swift; sourceTree = ""; }; + C1D15C7A1D7C9B7E0041D119 /* ActiveBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActiveBuilder.swift; sourceTree = ""; }; C1E867D51C3D7AEA00FD687A /* RegexParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegexParser.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -99,9 +103,11 @@ isa = PBXGroup; children = ( 8F0249A51B9989B1005D8035 /* ActiveLabel.h */, - 8F0249D21B998C00005D8035 /* ActiveLabel.swift */, 8F0249D41B998D21005D8035 /* ActiveType.swift */, + 8F0249D21B998C00005D8035 /* ActiveLabel.swift */, + C1D15C7A1D7C9B7E0041D119 /* ActiveBuilder.swift */, C1E867D51C3D7AEA00FD687A /* RegexParser.swift */, + C1D15C781D7C9B610041D119 /* StringTrimExtension.swift */, 8F0249A71B9989B1005D8035 /* Info.plist */, ); path = ActiveLabel; @@ -269,8 +275,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C1D15C791D7C9B610041D119 /* StringTrimExtension.swift in Sources */, 8F0249D31B998C00005D8035 /* ActiveLabel.swift in Sources */, 8F0249D51B998D21005D8035 /* ActiveType.swift in Sources */, + C1D15C7B1D7C9B7E0041D119 /* ActiveBuilder.swift in Sources */, C1E867D61C3D7AEA00FD687A /* RegexParser.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift new file mode 100644 index 00000000..741d9eb7 --- /dev/null +++ b/ActiveLabel/ActiveBuilder.swift @@ -0,0 +1,99 @@ +// +// ActiveBuilder.swift +// ActiveLabel +// +// Created by Pol Quintana on 04/09/16. +// Copyright © 2016 Optonaut. All rights reserved. +// + +import Foundation + +typealias ActiveFilterPredicate = (String -> Bool) + +struct ActiveBuilder { + + static func createElements(type: ActiveType, from text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + switch type { + case .Mention, .Hashtag: + return createElementsIgnoringFirstCharacter(from: text, for: type, range: range, filterPredicate: filterPredicate) + case .URL: + return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) + case .Custom: + return createElements(from: text, for: type, range: range, minLength: 1, filterPredicate: filterPredicate) + } + } + + static func createURLElements(from text: String, range: NSRange, maxChar: Int?) -> ([ElementTuple], String) { + let type = ActiveType.URL + var text = text + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) + let nsstring = text as NSString + var elements: [ElementTuple] = [] + + for match in matches where match.range.length > 2 { + let word = nsstring.substringWithRange(match.range) + .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + + guard let maxChar = maxChar where word.characters.count > maxChar else { + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) + continue + } + + let trimmedWord = word.trim(to: maxChar) + text = text.stringByReplacingOccurrencesOfString(word, withString: trimmedWord) + let element = ActiveElement.URL(original: word, trimmed: trimmedWord) + + let newRange = NSRange(location: match.range.location, length: trimmedWord.characters.count) + elements.append((newRange, element, type)) + } + return (elements, text) + } + + private static func createElements(from text: String, + for type: ActiveType, + range: NSRange, + minLength: Int = 2, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) + let nsstring = text as NSString + var elements: [ElementTuple] = [] + + for match in matches where match.range.length > minLength { + let word = nsstring.substringWithRange(match.range) + .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) + if filterPredicate?(word) ?? true { + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) + } + } + return elements + } + + private static func createElementsIgnoringFirstCharacter(from text: String, + for type: ActiveType, + range: NSRange, + filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { + let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) + let nsstring = text as NSString + var elements: [ElementTuple] = [] + + for match in matches where match.range.length > 2 { + let range = NSRange(location: match.range.location + 1, length: match.range.length - 1) + var word = nsstring.substringWithRange(range) + if word.hasPrefix("@") { + word.removeAtIndex(word.startIndex) + } + else if word.hasPrefix("#") { + word.removeAtIndex(word.startIndex) + } + + if filterPredicate?(word) ?? true { + let element = ActiveElement.create(with: type, text: word) + elements.append((match.range, element, type)) + } + } + return elements + } +} diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 24dff17f..435559ca 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -16,12 +16,14 @@ public protocol ActiveLabelDelegate: class { typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @IBDesignable public class ActiveLabel: UILabel { - + // MARK: - public properties public weak var delegate: ActiveLabelDelegate? public var enabledTypes: [ActiveType] = [.Mention, .Hashtag, .URL] - + + public var urlMaximumLength: Int? + @IBInspectable public var mentionColor: UIColor = .blueColor() { didSet { updateTextStorage(parseText: false) } } @@ -54,11 +56,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy public func handleMentionTap(handler: (String) -> ()) { mentionTapHandler = handler } - + public func handleHashtagTap(handler: (String) -> ()) { hashtagTapHandler = handler } - + public func handleURLTap(handler: (NSURL) -> ()) { urlTapHandler = handler } @@ -81,19 +83,19 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy override public var text: String? { didSet { updateTextStorage() } } - + override public var attributedText: NSAttributedString? { didSet { updateTextStorage() } } - + override public var font: UIFont! { didSet { updateTextStorage(parseText: false) } } - + override public var textColor: UIColor! { didSet { updateTextStorage(parseText: false) } } - + override public var textAlignment: NSTextAlignment { didSet { updateTextStorage(parseText: false)} } @@ -101,18 +103,18 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy public override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } - + public override var lineBreakMode: NSLineBreakMode { didSet { textContainer.lineBreakMode = lineBreakMode } } - + // MARK: - init functions override public init(frame: CGRect) { super.init(frame: frame) _customizing = false setupLabel() } - + required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) _customizing = false @@ -123,18 +125,18 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy super.awakeFromNib() updateTextStorage() } - + public override func drawTextInRect(rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) - + textContainer.size = rect.size let newOrigin = textOrigin(inRect: rect) - + layoutManager.drawBackgroundForGlyphRange(range, atPoint: newOrigin) layoutManager.drawGlyphsForGlyphRange(range, atPoint: newOrigin) } - - + + // MARK: - customzation public func customize(block: (label: ActiveLabel) -> ()) -> ActiveLabel { _customizing = true @@ -151,12 +153,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let size = layoutManager.usedRectForTextContainer(textContainer) return CGSize(width: ceil(size.width), height: ceil(size.height)) } - + // MARK: - touch events func onTouch(touch: UITouch) -> Bool { let location = touch.locationInView(self) var avoidSuperCall = false - + switch touch.phase { case .Began, .Moved: if let element = elementAtLocation(location) { @@ -172,14 +174,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } case .Ended: guard let selectedElement = selectedElement else { return avoidSuperCall } - + switch selectedElement.element { case .Mention(let userHandle): didTapMention(userHandle) case .Hashtag(let hashtag): didTapHashtag(hashtag) - case .URL(let url): didTapStringURL(url) + case .URL(let originalURL, _): didTapStringURL(originalURL) case .Custom(let element): didTap(element, for: selectedElement.type) } - + let when = dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))) dispatch_after(when, dispatch_get_main_queue()) { self.updateAttributesWhenSelected(false) @@ -192,14 +194,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .Stationary: break } - + return avoidSuperCall } - + // MARK: - private properties private var _customizing: Bool = true private var defaultCustomColor: UIColor = .blackColor() - + private var mentionTapHandler: ((String) -> ())? private var hashtagTapHandler: ((String) -> ())? private var urlTapHandler: ((NSURL) -> ())? @@ -224,7 +226,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy textContainer.maximumNumberOfLines = numberOfLines userInteractionEnabled = true } - + private func updateTextStorage(parseText parseText: Bool = true) { if _customizing { return } // clean up previous active elements @@ -234,14 +236,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setNeedsDisplay() return } - + let mutAttrString = addLineBreak(attributedText) if parseText { clearActiveElements() - parseTextAndExtractActiveElements(mutAttrString) + let newString = parseTextAndExtractActiveElements(mutAttrString) + mutAttrString.mutableString.setString(newString) } - + addLinkAttribute(mutAttrString) textStorage.setAttributedString(mutAttrString) setNeedsDisplay() @@ -260,40 +263,50 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y return CGPoint(x: rect.origin.x, y: glyphOriginY) } - + /// add link attribute private func addLinkAttribute(mutAttrString: NSMutableAttributedString) { var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range) - + attributes[NSFontAttributeName] = font! attributes[NSForegroundColorAttributeName] = textColor mutAttrString.addAttributes(attributes, range: range) - + attributes[NSForegroundColorAttributeName] = mentionColor - + for (type, elements) in activeElements { - + switch type { case .Mention: attributes[NSForegroundColorAttributeName] = mentionColor case .Hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor case .URL: attributes[NSForegroundColorAttributeName] = URLColor case .Custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } - + for element in elements { mutAttrString.setAttributes(attributes, range: element.range) } } } - + /// use regex check all link ranges - private func parseTextAndExtractActiveElements(attrString: NSAttributedString) { - let textString = attrString.string - let textLength = textString.utf16.count - let textRange = NSRange(location: 0, length: textLength) + private func parseTextAndExtractActiveElements(attrString: NSMutableAttributedString) -> String{ + var textString = attrString.string + var textLength = textString.utf16.count + var textRange = NSRange(location: 0, length: textLength) - for type in enabledTypes { + if enabledTypes.contains(.URL) { + let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maxChar: urlMaximumLength) + let urlElements = tuple.0 + let finalText = tuple.1 + textString = finalText + textLength = textString.utf16.count + textRange = NSRange(location: 0, length: textLength) + activeElements[.URL] = urlElements + } + + for type in enabledTypes where type != .URL{ var filter: ((String) -> Bool)? = nil if type == .Mention { filter = mentionFilterPredicate @@ -303,32 +316,34 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let hashtagElements = ActiveBuilder.createElements(type, from: textString, range: textRange, filterPredicate: filter) activeElements[type] = hashtagElements } + + return textString } - + /// add line break mode private func addLineBreak(attrString: NSAttributedString) -> NSMutableAttributedString { let mutAttrString = NSMutableAttributedString(attributedString: attrString) - + var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributesAtIndex(0, effectiveRange: &range) - + let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = CGFloat(lineSpacing) - + attributes[NSParagraphStyleAttributeName] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) - + return mutAttrString } - + private func updateAttributesWhenSelected(isSelected: Bool) { guard let selectedElement = selectedElement else { return } - + var attributes = textStorage.attributesAtIndex(0, effectiveRange: nil) let type = selectedElement.type @@ -353,12 +368,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } attributes[NSForegroundColorAttributeName] = unselectedColor } - + textStorage.addAttributes(attributes, range: selectedElement.range) - + setNeedsDisplay() } - + private func elementAtLocation(location: CGPoint) -> ElementTuple? { guard textStorage.length > 0 else { return nil @@ -370,19 +385,19 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy guard boundingRect.contains(correctLocation) else { return nil } - + let index = layoutManager.glyphIndexForPoint(correctLocation, inTextContainer: textContainer) - + for element in activeElements.map({ $0.1 }).flatten() { if index >= element.range.location && index <= element.range.location + element.range.length { return element } } - + return nil } - - + + //MARK: - Handle UI Responder touches public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { guard let touch = touches.first else { return } @@ -395,19 +410,19 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if onTouch(touch) { return } super.touchesMoved(touches, withEvent: event) } - + public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { guard let touch = touches?.first else { return } onTouch(touch) super.touchesCancelled(touches, withEvent: event) } - + public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesEnded(touches, withEvent: event) } - + //MARK: - ActiveLabel handler private func didTapMention(username: String) { guard let mentionHandler = mentionTapHandler else { @@ -416,7 +431,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } mentionHandler(username) } - + private func didTapHashtag(hashtag: String) { guard let hashtagHandler = hashtagTapHandler else { delegate?.didSelectText(hashtag, type: .Hashtag) @@ -424,7 +439,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } hashtagHandler(hashtag) } - + private func didTapStringURL(stringURL: String) { guard let urlHandler = urlTapHandler, let url = NSURL(string: stringURL) else { delegate?.didSelectText(stringURL, type: .URL) @@ -443,15 +458,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } extension ActiveLabel: UIGestureRecognizerDelegate { - + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOfGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index d2393267..043fc6b9 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -11,14 +11,14 @@ import Foundation enum ActiveElement { case Mention(String) case Hashtag(String) - case URL(String) + case URL(original: String, trimmed: String) case Custom(String) static func create(with activeType: ActiveType, text: String) -> ActiveElement { switch activeType { case .Mention: return Mention(text) case .Hashtag: return Hashtag(text) - case .URL: return URL(text) + case .URL: return URL(original: text, trimmed: text) case .Custom: return Custom(text) } } @@ -60,66 +60,3 @@ public func ==(lhs: ActiveType, rhs: ActiveType) -> Bool { default: return false } } - -typealias ActiveFilterPredicate = (String -> Bool) - -struct ActiveBuilder { - - static func createElements(type: ActiveType, from text: String, range: NSRange, filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { - switch type { - case .Mention, .Hashtag: - return createElementsIgnoringFirstCharacter(from: text, for: type, range: range, filterPredicate: filterPredicate) - case .URL: - return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) - case .Custom: - return createElements(from: text, for: type, range: range, minLength: 1, filterPredicate: filterPredicate) - } - } - - private static func createElements(from text: String, - for type: ActiveType, - range: NSRange, - minLength: Int = 2, - filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { - let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) - let nsstring = text as NSString - var elements: [ElementTuple] = [] - - for match in matches where match.range.length > minLength { - let word = nsstring.substringWithRange(match.range) - .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - if filterPredicate?(word) ?? true { - let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) - } - } - return elements - } - - private static func createElementsIgnoringFirstCharacter(from text: String, - for type: ActiveType, - range: NSRange, - filterPredicate: ActiveFilterPredicate?) -> [ElementTuple] { - let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) - let nsstring = text as NSString - var elements: [ElementTuple] = [] - - for match in matches where match.range.length > 2 { - let range = NSRange(location: match.range.location + 1, length: match.range.length - 1) - var word = nsstring.substringWithRange(range) - if word.hasPrefix("@") { - word.removeAtIndex(word.startIndex) - } - else if word.hasPrefix("#") { - word.removeAtIndex(word.startIndex) - } - - if filterPredicate?(word) ?? true { - let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) - } - } - return elements - - } -} diff --git a/ActiveLabel/StringTrimExtension.swift b/ActiveLabel/StringTrimExtension.swift new file mode 100644 index 00000000..beb4f434 --- /dev/null +++ b/ActiveLabel/StringTrimExtension.swift @@ -0,0 +1,16 @@ +// +// StringTrimExtension.swift +// ActiveLabel +// +// Created by Pol Quintana on 04/09/16. +// Copyright © 2016 Optonaut. All rights reserved. +// + +import Foundation + +extension String { + + func trim(to maximumCharacters: Int) -> String { + return substringToIndex(startIndex.advancedBy(maximumCharacters)) + "..." + } +} diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index bb864cc5..e105e9a6 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -22,6 +22,8 @@ class ViewController: UIViewController { label.enabledTypes.append(customType) label.enabledTypes.append(customType2) + label.urlMaximumLength = 28 + label.customize { label in label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like" + " this one: http://optonaut.co. Now it also supports custom patterns -> are" From f67dae307dfbce7aa9f5cef5c933a44a078986bc Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 20:23:48 +0200 Subject: [PATCH 041/116] Update demo text --- ActiveLabelDemo/ViewController.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index e105e9a6..7fd2d964 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -22,11 +22,12 @@ class ViewController: UIViewController { label.enabledTypes.append(customType) label.enabledTypes.append(customType2) - label.urlMaximumLength = 28 + label.urlMaximumLength = 31 label.customize { label in label.text = "This is a post with #multiple #hashtags and a @userhandle. Links are also supported like" + - " this one: http://optonaut.co. Now it also supports custom patterns -> are" + " this one: http://optonaut.co. Now it also supports custom patterns -> are\n\n" + + "Let's trim a long link: \nhttps://twitter.com/twicket_app/status/649678392372121601" label.numberOfLines = 0 label.lineSpacing = 4 From 41b78e451a2b36e6e7eb80176f931164c5a89590 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 20:27:06 +0200 Subject: [PATCH 042/116] Fix broken tests --- ActiveLabelTests/ActiveTypeTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 4f68ea7e..bac4d8af 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -35,7 +35,7 @@ class ActiveTypeTests: XCTestCase { switch currentElement { case .Mention(let mention): return mention case .Hashtag(let hashtag): return hashtag - case .URL(let url): return url + case .URL(let url, _): return url case .Custom(let element): return element } } From 23945e9798a5d2b3eb9828340837caff8df4c337 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 20:35:22 +0200 Subject: [PATCH 043/116] Fix leading dot not being highlighted --- ActiveLabel/ActiveBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 741d9eb7..30668495 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -44,7 +44,7 @@ struct ActiveBuilder { text = text.stringByReplacingOccurrencesOfString(word, withString: trimmedWord) let element = ActiveElement.URL(original: word, trimmed: trimmedWord) - let newRange = NSRange(location: match.range.location, length: trimmedWord.characters.count) + let newRange = NSRange(location: match.range.location, length: trimmedWord.characters.count + 1) elements.append((newRange, element, type)) } return (elements, text) From 485285e63931012e4a65fbc661263fddf59f4028 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 20:42:09 +0200 Subject: [PATCH 044/116] Prepare for 0.6.2 --- ActiveLabel.podspec | 3 ++- README.md | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index ee021389..12f74d2c 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.6.1' + s.version = '0.6.2' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' @@ -16,6 +16,7 @@ Pod::Spec.new do |s| * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types + * Ability to trim urls * Super easy to use and lightweight * Works as UILabel drop-in replacement * Well tested and documented diff --git a/README.md b/README.md index ab9361e7..a58ed454 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types +* Ability to trim urls * Super easy to use and lightweight * Works as `UILabel` drop-in replacement * Well tested and documented @@ -79,6 +80,17 @@ Example: ``` +## Trim long urls + +You have the possiblity to set the maximum lenght a url can have; + +``` + label.urlMaximumLength = 30 +``` + +From now on, a url that's bigger than that, will be trimmed. + +`https://afancyurl.com/whatever` -> `https://afancyurl.com/wh...` ## API From 1f9ceb52b2ff5e88af9ab403ee2782cf59b50bdd Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 21:11:35 +0200 Subject: [PATCH 045/116] Update Swift version in Readme & podspec --- ActiveLabel.podspec | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 12f74d2c..2479290c 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 2+ + * Up-to-date: Swift 3 * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types diff --git a/README.md b/README.md index a58ed454..a199eff5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 2+ +* Swift 3 * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types From f27a513aa475edfecdea3243a5871d98628e1c0b Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 4 Sep 2016 22:53:17 +0200 Subject: [PATCH 046/116] Fix crash due to range out of bounds - caused by #107 --- ActiveLabel/ActiveBuilder.swift | 7 ++++--- ActiveLabel/ActiveLabel.swift | 5 ++++- ActiveLabelTests/ActiveTypeTests.swift | 8 ++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 30668495..59de2648 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -35,16 +35,17 @@ struct ActiveBuilder { .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) guard let maxChar = maxChar where word.characters.count > maxChar else { + let newRange = (text as NSString).rangeOfString(word) let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) + elements.append((newRange, element, type)) continue } let trimmedWord = word.trim(to: maxChar) text = text.stringByReplacingOccurrencesOfString(word, withString: trimmedWord) - let element = ActiveElement.URL(original: word, trimmed: trimmedWord) - let newRange = NSRange(location: match.range.location, length: trimmedWord.characters.count + 1) + let newRange = (text as NSString).rangeOfString(trimmedWord) + let element = ActiveElement.URL(original: word, trimmed: trimmedWord) elements.append((newRange, element, type)) } return (elements, text) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 435559ca..0584494a 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -247,6 +247,9 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy addLinkAttribute(mutAttrString) textStorage.setAttributedString(mutAttrString) + _customizing = true + text = mutAttrString.string + _customizing = false setNeedsDisplay() } @@ -291,7 +294,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } /// use regex check all link ranges - private func parseTextAndExtractActiveElements(attrString: NSMutableAttributedString) -> String{ + private func parseTextAndExtractActiveElements(attrString: NSMutableAttributedString) -> String { var textString = attrString.string var textLength = textString.utf16.count var textRange = NSRange(location: 0, length: textLength) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index bac4d8af..7b76d7f5 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -326,4 +326,12 @@ class ActiveTypeTests: XCTestCase { XCTAssertEqual(currentElementString, "are") XCTAssertEqual(currentElementType, customEmptyType) } + + func testStringTrimming() { + let text = "Tweet with long url: https://twitter.com/twicket_app/status/649678392372121601 and short url: https://hello.co" + label.urlMaximumLength = 30 + label.text = text + + XCTAssertNotEqual(text.characters.count, label.text!.characters.count) + } } From f609665a3bb3acf5fb2604fd34429c48c012a8c8 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Mon, 5 Sep 2016 18:50:48 +0200 Subject: [PATCH 047/116] Improve performance for non urlMaximumLength users --- ActiveLabel/ActiveBuilder.swift | 10 +++++----- ActiveLabel/ActiveLabel.swift | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 59de2648..1aae07d3 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -23,7 +23,7 @@ struct ActiveBuilder { } } - static func createURLElements(from text: String, range: NSRange, maxChar: Int?) -> ([ElementTuple], String) { + static func createURLElements(from text: String, range: NSRange, maximumLenght: Int?) -> ([ElementTuple], String) { let type = ActiveType.URL var text = text let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) @@ -34,14 +34,14 @@ struct ActiveBuilder { let word = nsstring.substringWithRange(match.range) .stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) - guard let maxChar = maxChar where word.characters.count > maxChar else { - let newRange = (text as NSString).rangeOfString(word) + guard let maxLenght = maximumLenght where word.characters.count > maxLenght else { + let range = maximumLenght == nil ? match.range : (text as NSString).rangeOfString(word) let element = ActiveElement.create(with: type, text: word) - elements.append((newRange, element, type)) + elements.append((range, element, type)) continue } - let trimmedWord = word.trim(to: maxChar) + let trimmedWord = word.trim(to: maxLenght) text = text.stringByReplacingOccurrencesOfString(word, withString: trimmedWord) let newRange = (text as NSString).rangeOfString(trimmedWord) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 0584494a..88e56f56 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -300,7 +300,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var textRange = NSRange(location: 0, length: textLength) if enabledTypes.contains(.URL) { - let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maxChar: urlMaximumLength) + let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maximumLenght: urlMaximumLength) let urlElements = tuple.0 let finalText = tuple.1 textString = finalText From 80fae948262087ebf9006d5d7c454c969aa88427 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Mon, 5 Sep 2016 19:01:48 +0200 Subject: [PATCH 048/116] Merge + adapt to Swift 3 master changes related to url max length --- ActiveLabel/ActiveBuilder.swift | 13 +++++++------ ActiveLabel/ActiveLabel.swift | 5 ++++- ActiveLabelTests/ActiveTypeTests.swift | 8 ++++++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 5f54ae94..600abd25 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -23,7 +23,7 @@ struct ActiveBuilder { } } - static func createURLElements(from text: String, range: NSRange, maxChar: Int?) -> ([ElementTuple], String) { + static func createURLElements(from text: String, range: NSRange, maximumLenght: Int?) -> ([ElementTuple], String) { let type = ActiveType.url var text = text let matches = RegexParser.getElements(from: text, with: type.pattern, range: range) @@ -34,17 +34,18 @@ struct ActiveBuilder { let word = nsstring.substring(with: match.range) .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - guard let maxChar = maxChar, word.characters.count > maxChar else { + guard let maxLenght = maximumLenght, word.characters.count > maxLenght else { + let range = maximumLenght == nil ? match.range : (text as NSString).range(of: word) let element = ActiveElement.create(with: type, text: word) - elements.append((match.range, element, type)) + elements.append((range, element, type)) continue } - let trimmedWord = word.trim(to: maxChar) + let trimmedWord = word.trim(to: maxLenght) text = text.replacingOccurrences(of: word, with: trimmedWord) - let element = ActiveElement.url(original: word, trimmed: trimmedWord) - let newRange = NSRange(location: match.range.location, length: trimmedWord.characters.count + 1) + let newRange = (text as NSString).range(of: trimmedWord) + let element = ActiveElement.url(original: word, trimmed: trimmedWord) elements.append((newRange, element, type)) } return (elements, text) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 9f664c20..097ebc84 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -249,6 +249,9 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy addLinkAttribute(mutAttrString) textStorage.setAttributedString(mutAttrString) + _customizing = true + text = mutAttrString.string + _customizing = false setNeedsDisplay() } @@ -299,7 +302,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var textRange = NSRange(location: 0, length: textLength) if enabledTypes.contains(.url) { - let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maxChar: urlMaximumLength) + let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maximumLenght: urlMaximumLength) let urlElements = tuple.0 let finalText = tuple.1 textString = finalText diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 5d1bb1eb..d3a844a9 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -326,4 +326,12 @@ class ActiveTypeTests: XCTestCase { XCTAssertEqual(currentElementString, "are") XCTAssertEqual(currentElementType, customEmptyType) } + + func testStringTrimming() { + let text = "Tweet with long url: https://twitter.com/twicket_app/status/649678392372121601 and short url: https://hello.co" + label.urlMaximumLength = 30 + label.text = text + + XCTAssertNotEqual(text.characters.count, label.text!.characters.count) + } } From 82618b00d871d0ccd61452466fc0c4f0234c2f52 Mon Sep 17 00:00:00 2001 From: Abdul Moiz Date: Mon, 26 Sep 2016 10:58:14 -0400 Subject: [PATCH 049/116] Different font for mention, highlight and URLs. --- ActiveLabel/ActiveLabel.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 88e56f56..862f40f4 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -51,6 +51,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy @IBInspectable public var lineSpacing: Float = 0 { didSet { updateTextStorage(parseText: false) } } + @IBInspectable public var highlightfontName: String = "" { + didSet { updateTextStorage(parseText: false) } + } + @IBInspectable public var highlightfontSize: CGFloat = 12 { + didSet { updateTextStorage(parseText: false) } + } + @IBInspectable public var useDifferentHighlightFont: Bool = false { + didSet { updateTextStorage(parseText: false) } + } // MARK: - public methods public func handleMentionTap(handler: (String) -> ()) { @@ -286,6 +295,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .URL: attributes[NSForegroundColorAttributeName] = URLColor case .Custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } + + if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) where useDifferentHighlightFont == true { + attributes[NSFontAttributeName] = highlightFont + } for element in elements { mutAttrString.setAttributes(attributes, range: element.range) @@ -371,6 +384,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } attributes[NSForegroundColorAttributeName] = unselectedColor } + + if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) where useDifferentHighlightFont == true { + attributes[NSFontAttributeName] = highlightFont + } textStorage.addAttributes(attributes, range: selectedElement.range) From db97681401f8ed78056718048c4783dd557c1c9b Mon Sep 17 00:00:00 2001 From: Abdul Moiz Date: Tue, 27 Sep 2016 10:50:52 -0400 Subject: [PATCH 050/116] removed property useDifferentHighlightFont --- ActiveLabel/ActiveLabel.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 862f40f4..25764aa0 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -57,9 +57,6 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy @IBInspectable public var highlightfontSize: CGFloat = 12 { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var useDifferentHighlightFont: Bool = false { - didSet { updateTextStorage(parseText: false) } - } // MARK: - public methods public func handleMentionTap(handler: (String) -> ()) { @@ -296,7 +293,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .Custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } - if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) where useDifferentHighlightFont == true { + if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) { attributes[NSFontAttributeName] = highlightFont } @@ -385,7 +382,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy attributes[NSForegroundColorAttributeName] = unselectedColor } - if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) where useDifferentHighlightFont == true { + if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) { attributes[NSFontAttributeName] = highlightFont } From 065f46e0847582c282ff5413c9ef90f93da64e60 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 14 Oct 2016 22:09:50 +0200 Subject: [PATCH 051/116] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a199eff5..be669ffb 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ import ActiveLabel let label = ActiveLabel() label.numberOfLines = 0 -label.enabledTypes = [.Mention, .Hashtag, .URL] +label.enabledTypes = [.mention, .hashtag, .url] label.text = "This is a post with #hashtags and a @userhandle." -label.textColor = .blackColor() +label.textColor = .black label.handleHashtagTap { hashtag in print("Success. You just tapped the \(hashtag) hashtag") } @@ -34,11 +34,11 @@ label.handleHashtagTap { hashtag in ## Custom types ```swift - let customType = ActiveType.Custom(pattern: "\\swith\\b") //Regex that looks for "with" - label.enabledTypes = [.Mention, .Hashtag, .URL, customType] + let customType = ActiveType.custom(pattern: "\\swith\\b") //Regex that looks for "with" + label.enabledTypes = [.mention, .hashtag, .url, customType] - label.customColor[customType] = UIColor.purpleColor() - label.customSelectedColor[customType] = UIColor.greenColor() + label.customColor[customType] = UIColor.purple + label.customSelectedColor[customType] = UIColor.green label.handleCustomTap(for: customType) { element in print("Custom type tapped: \(element)") @@ -50,7 +50,7 @@ label.handleHashtagTap { hashtag in By default, an ActiveLabel instance has the following configuration ```swift - label.enabledTypes = [.Mention, .Hashtag, .URL] + label.enabledTypes = [.mention, .hashtag, .url] ``` But feel free to enable/disable to fit your requirements @@ -119,7 +119,7 @@ label.handleHashtagTap { hashtag in print("\(hashtag) tapped") } ##### `handleURLTap: (NSURL) -> ()` ```swift -label.handleURLTap { url in UIApplication.sharedApplication().openURL(url) } +label.handleURLTap { url in UIApplication.shared.openURL(url) } ``` ##### `handleCustomTap(for type: ActiveType, handler: (String) -> ())` From 302818137dd0f45a435eecf96cb5b22f11ee5d51 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 14 Oct 2016 22:11:53 +0200 Subject: [PATCH 052/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 2479290c..d1f37dbb 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.6.2' + s.version = '0.7.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From c7c5cf7d893c882a6f2d9270b2fe2e40953ba06f Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Fri, 14 Oct 2016 22:17:53 +0200 Subject: [PATCH 053/116] Add .swift-version --- .swift-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 00000000..9f55b2cc --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0 From dcfba9ae9bdd9dde56f6ebfb7e456652200ba086 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 16 Oct 2016 14:04:33 +0200 Subject: [PATCH 054/116] Update project configuration --- ActiveLabel.xcodeproj/project.pbxproj | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index de24ab4f..78bdd57a 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ C1D15C791D7C9B610041D119 /* StringTrimExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D15C781D7C9B610041D119 /* StringTrimExtension.swift */; }; C1D15C7B1D7C9B7E0041D119 /* ActiveBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D15C7A1D7C9B7E0041D119 /* ActiveBuilder.swift */; }; C1E867D61C3D7AEA00FD687A /* RegexParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1E867D51C3D7AEA00FD687A /* RegexParser.swift */; }; + E267FA251DB3A34900EEAC4C /* ActiveLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F0249A21B9989B1005D8035 /* ActiveLabel.framework */; }; + E267FA261DB3A34900EEAC4C /* ActiveLabel.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8F0249A21B9989B1005D8035 /* ActiveLabel.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -30,8 +32,29 @@ remoteGlobalIDString = 8F0249A11B9989B1005D8035; remoteInfo = ActiveLabel; }; + E267FA271DB3A34900EEAC4C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8F0249991B9989B1005D8035 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8F0249A11B9989B1005D8035; + remoteInfo = ActiveLabel; + }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + E267FA291DB3A34900EEAC4C /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + E267FA261DB3A34900EEAC4C /* ActiveLabel.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 8F0249A21B9989B1005D8035 /* ActiveLabel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ActiveLabel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8F0249A51B9989B1005D8035 /* ActiveLabel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActiveLabel.h; sourceTree = ""; }; @@ -73,6 +96,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E267FA251DB3A34900EEAC4C /* ActiveLabel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -192,10 +216,12 @@ 8F0249BC1B998A66005D8035 /* Sources */, 8F0249BD1B998A66005D8035 /* Frameworks */, 8F0249BE1B998A66005D8035 /* Resources */, + E267FA291DB3A34900EEAC4C /* Embed Frameworks */, ); buildRules = ( ); dependencies = ( + E267FA281DB3A34900EEAC4C /* PBXTargetDependency */, ); name = ActiveLabelDemo; productName = ActiveLabelDemo; @@ -310,6 +336,11 @@ target = 8F0249A11B9989B1005D8035 /* ActiveLabel */; targetProxy = 8F0249AE1B9989B1005D8035 /* PBXContainerItemProxy */; }; + E267FA281DB3A34900EEAC4C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8F0249A11B9989B1005D8035 /* ActiveLabel */; + targetProxy = E267FA271DB3A34900EEAC4C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -492,6 +523,7 @@ 8F0249CF1B998A66005D8035 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = ActiveLabelDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -505,6 +537,7 @@ 8F0249D01B998A66005D8035 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = ActiveLabelDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; From a23081d3d18e3f17f82206670d428f03acb6afd6 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 16 Oct 2016 14:04:59 +0200 Subject: [PATCH 055/116] Cache regular expressions to avoid them to be created on the fly - #114 --- ActiveLabel/RegexParser.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index 998dc0db..b0ad4a4b 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -16,9 +16,21 @@ struct RegexParser { "((https?://|www\\.|pic\\.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" + private static var cachedRegularExpressions: [String : NSRegularExpression] = [:] static func getElements(from text: String, with pattern: String, range: NSRange) -> [NSTextCheckingResult]{ - guard let elementRegex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else { return [] } + guard let elementRegex = regularExpression(for: pattern) else { return [] } return elementRegex.matches(in: text, options: [], range: range) } + + private static func regularExpression(for pattern: String) -> NSRegularExpression? { + if let regex = cachedRegularExpressions[pattern] { + return regex + } else if let createdRegex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) { + cachedRegularExpressions[pattern] = createdRegex + return createdRegex + } else { + return nil + } + } } From 332db737a1c3e460a04eeb604a192bcbd2acdbce Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Sun, 16 Oct 2016 14:15:11 +0200 Subject: [PATCH 056/116] Update travis.yml to use scan --- .travis.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9a17e95f..790372a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: objective-c - -xcode_project: ActiveLabel.xcodeproj -xcode_scheme: ActiveLabel osx_image: xcode8 -xcode_sdk: iphonesimulator10.0 +before_install: +- gem install scan +- scan --version +install: true +script: +- scan -p "ActiveLabel.xcodeproj" -s "ActiveLabel" \ No newline at end of file From 66e67d528751843a5af64ac55abd002eb1adbc29 Mon Sep 17 00:00:00 2001 From: Abdul Moiz Date: Tue, 25 Oct 2016 11:57:01 -0400 Subject: [PATCH 057/116] Here are the changes you requested. --- ActiveLabel/ActiveLabel.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 3dab057d..f8c6f90a 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -51,12 +51,18 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy @IBInspectable open var lineSpacing: Float = 0 { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var highlightfontName: String = "" { + @IBInspectable public var highlightFontName: String? = nil { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var highlightfontSize: CGFloat = 12 { + @IBInspectable public var highlightFontSize: CGFloat? = nil { didSet { updateTextStorage(parseText: false) } } + + // MARK: - Computed Properties + private var hightlightFont: UIFont? { + guard let highlightFontName = highlightFontName, let highlightFontSize = highlightFontSize else { return nil } + return UIFont(name: highlightFontName, size: highlightFontSize) + } // MARK: - public methods open func handleMentionTap(_ handler: @escaping (String) -> ()) { @@ -295,7 +301,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor } - if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) { + if let highlightFont = hightlightFont { attributes[NSFontAttributeName] = highlightFont } @@ -384,7 +390,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy attributes[NSForegroundColorAttributeName] = unselectedColor } - if let highlightFont = UIFont(name: highlightfontName, size: highlightfontSize) { + if let highlightFont = hightlightFont { attributes[NSFontAttributeName] = highlightFont } From 95963d6a122ae34ab105b9168d6c985a8177f991 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Mon, 10 Oct 2016 21:41:21 -0500 Subject: [PATCH 058/116] Public configureLinkAttributes to full customize the attributed string --- ActiveLabel/ActiveLabel.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index f8c6f90a..b18dca06 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -280,6 +280,9 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y return CGPoint(x: rect.origin.x, y: glyphOriginY) } + + public typealias ConfigureLinkAttribute = (ActiveType, [String: Any]) -> ([String: Any]) + public var configureLinkAttribute: ConfigureLinkAttribute? /// add link attribute fileprivate func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { @@ -304,6 +307,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if let highlightFont = hightlightFont { attributes[NSFontAttributeName] = highlightFont } + + if let configureLinkAttribute = configureLinkAttribute { + attributes = configureLinkAttribute(type, attributes) + } for element in elements { mutAttrString.setAttributes(attributes, range: element.range) From 8bf643fe8b304ea81c1ffd14e492c5f69c327776 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Mon, 10 Oct 2016 23:07:39 -0500 Subject: [PATCH 059/116] Added functions to clear custom tap handlers --- ActiveLabel/ActiveLabel.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index b18dca06..764b8d2a 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -80,6 +80,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy open func handleCustomTap(for type: ActiveType, handler: @escaping (String) -> ()) { customTapHandlers[type] = handler } + + open func removeHandleCustomTap(for type: ActiveType) { + customTapHandlers[type] = nil + } + + open func removeAllHandleCustomTaps() { + customTapHandlers.removeAll() + } open func filterMention(_ predicate: @escaping (String) -> Bool) { mentionFilterPredicate = predicate @@ -281,8 +289,8 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy return CGPoint(x: rect.origin.x, y: glyphOriginY) } - public typealias ConfigureLinkAttribute = (ActiveType, [String: Any]) -> ([String: Any]) - public var configureLinkAttribute: ConfigureLinkAttribute? + public typealias ConfigureLinkAttribute = (ActiveType, [String: Any]) -> ([String: Any]) + public var configureLinkAttribute: ConfigureLinkAttribute? /// add link attribute fileprivate func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { @@ -308,9 +316,9 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy attributes[NSFontAttributeName] = highlightFont } - if let configureLinkAttribute = configureLinkAttribute { - attributes = configureLinkAttribute(type, attributes) - } + if let configureLinkAttribute = configureLinkAttribute { + attributes = configureLinkAttribute(type, attributes) + } for element in elements { mutAttrString.setAttributes(attributes, range: element.range) From bc29d1416ed1fdc4c03409d6ce0c2e929b097571 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 25 Oct 2016 06:21:10 -0500 Subject: [PATCH 060/116] Fixes from PR review --- ActiveLabel/ActiveLabel.swift | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 764b8d2a..9cf97697 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -13,6 +13,7 @@ public protocol ActiveLabelDelegate: class { func didSelect(_ text: String, type: ActiveType) } +public typealias ConfigureLinkAttribute = (ActiveType, [String : Any]) -> ([String : Any]) typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @IBDesignable open class ActiveLabel: UILabel { @@ -23,6 +24,8 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy open var enabledTypes: [ActiveType] = [.mention, .hashtag, .url] open var urlMaximumLength: Int? + + open var configureLinkAttribute: ConfigureLinkAttribute? @IBInspectable open var mentionColor: UIColor = .blue { didSet { updateTextStorage(parseText: false) } @@ -81,12 +84,17 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy customTapHandlers[type] = handler } - open func removeHandleCustomTap(for type: ActiveType) { - customTapHandlers[type] = nil - } - - open func removeAllHandleCustomTaps() { - customTapHandlers.removeAll() + open func removeHandle(for type: ActiveType) { + switch type { + case .hashtag: + hashtagTapHandler = nil + case .mention: + mentionTapHandler = nil + case .url: + urlTapHandler = nil + case .custom: + customTapHandlers[type] = nil + } } open func filterMention(_ predicate: @escaping (String) -> Bool) { @@ -228,7 +236,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy fileprivate var hashtagTapHandler: ((String) -> ())? fileprivate var urlTapHandler: ((URL) -> ())? fileprivate var customTapHandlers: [ActiveType : ((String) -> ())] = [:] - + fileprivate var mentionFilterPredicate: ((String) -> Bool)? fileprivate var hashtagFilterPredicate: ((String) -> Bool)? @@ -288,9 +296,6 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y return CGPoint(x: rect.origin.x, y: glyphOriginY) } - - public typealias ConfigureLinkAttribute = (ActiveType, [String: Any]) -> ([String: Any]) - public var configureLinkAttribute: ConfigureLinkAttribute? /// add link attribute fileprivate func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { From bab356f5437dd7bd2090e50f669b73f696febb8f Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 25 Oct 2016 08:04:25 -0500 Subject: [PATCH 061/116] Added tests and configureLinkAttributes now supports isSelected --- ActiveLabel/ActiveLabel.swift | 16 +++++++++++-- ActiveLabelDemo/ViewController.swift | 14 +++++++++++ ActiveLabelTests/ActiveTypeTests.swift | 32 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 9cf97697..de48f667 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -13,7 +13,7 @@ public protocol ActiveLabelDelegate: class { func didSelect(_ text: String, type: ActiveType) } -public typealias ConfigureLinkAttribute = (ActiveType, [String : Any]) -> ([String : Any]) +public typealias ConfigureLinkAttribute = (ActiveType, [String : Any], Bool) -> ([String : Any]) typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @IBDesignable open class ActiveLabel: UILabel { @@ -246,8 +246,16 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy fileprivate lazy var layoutManager = NSLayoutManager() fileprivate lazy var textContainer = NSTextContainer() lazy var activeElements = [ActiveType: [ElementTuple]]() + + // MARK: - internal (used for testing) + + // Needed for unit testing + internal var textStorageAttributedString: NSAttributedString { + return NSAttributedString(attributedString: textStorage) + } // MARK: - helper functions + fileprivate func setupLabel() { textStorage.addLayoutManager(layoutManager) layoutManager.addTextContainer(textContainer) @@ -322,7 +330,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } if let configureLinkAttribute = configureLinkAttribute { - attributes = configureLinkAttribute(type, attributes) + attributes = configureLinkAttribute(type, attributes, false) } for element in elements { @@ -413,6 +421,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if let highlightFont = hightlightFont { attributes[NSFontAttributeName] = highlightFont } + + if let configureLinkAttribute = configureLinkAttribute { + attributes = configureLinkAttribute(type, attributes, isSelected) + } textStorage.addAttributes(attributes, range: selectedElement.range) diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index 9e6e0165..17bee470 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -18,9 +18,11 @@ class ViewController: UIViewController { let customType = ActiveType.custom(pattern: "\\sare\\b") //Looks for "are" let customType2 = ActiveType.custom(pattern: "\\sit\\b") //Looks for "it" + let customType3 = ActiveType.custom(pattern: "\\ssupports\\b") //Looks for "supports" label.enabledTypes.append(customType) label.enabledTypes.append(customType2) + label.enabledTypes.append(customType3) label.urlMaximumLength = 31 @@ -47,9 +49,21 @@ class ViewController: UIViewController { label.customSelectedColor[customType] = UIColor.green label.customColor[customType2] = UIColor.magenta label.customSelectedColor[customType2] = UIColor.green + + label.configureLinkAttribute = { (type, attributes, isSelected) in + var atts = attributes + switch type { + case customType3: + atts[NSFontAttributeName] = isSelected ? UIFont.boldSystemFont(ofSize: 16) : UIFont.boldSystemFont(ofSize: 14) + default: () + } + + return atts + } label.handleCustomTap(for: customType) { self.alert("Custom type", message: $0) } label.handleCustomTap(for: customType2) { self.alert("Custom type", message: $0) } + label.handleCustomTap(for: customType3) { self.alert("Custom type", message: $0) } } label.frame = CGRect(x: 20, y: 40, width: view.frame.width - 40, height: 300) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index d3a844a9..2743ce4c 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -222,6 +222,38 @@ class ActiveTypeTests: XCTestCase { label.text = "google" XCTAssertEqual(activeElements.count, 0) } + + func testConfigureLinkAttributes() { + // Customize label + let newType = ActiveType.custom(pattern: "\\sare\\b") + label.customize { label in + label.enabledTypes = [newType] + + // Configure "are" to be system font / bold / 14 + label.configureLinkAttribute = { type, attributes, isSelected in + var atts = attributes + if case newType = type { + atts[NSFontAttributeName] = UIFont.boldSystemFont(ofSize: 14) + } + + return atts + } + label.text = "we are one" + } + + // Find attributed text + let range = (label.text! as NSString).range(of: "are") + let areText = label.textStorageAttributedString.attributedSubstring(from: range) + + // Enumber after attributes and find our font + var foundCustomAttributedStyling = false + areText.enumerateAttributes(in: NSRange(location: 0, length: areText.length), options: [.longestEffectiveRangeNotRequired], using: { (attributes, range, stop) in + foundCustomAttributedStyling = attributes[NSFontAttributeName] as? UIFont == UIFont.boldSystemFont(ofSize: 14) + }) + + XCTAssertTrue(foundCustomAttributedStyling) + } + func testFiltering() { label.text = "@user #tag" From ae2430407007134d5652cbc71cd3b739ea524810 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 25 Oct 2016 11:52:11 -0500 Subject: [PATCH 062/116] Updated tests --- ActiveLabel/ActiveLabel.swift | 17 +++++--------- ActiveLabelTests/ActiveTypeTests.swift | 31 +++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index de48f667..54c27cff 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -232,27 +232,20 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy fileprivate var _customizing: Bool = true fileprivate var defaultCustomColor: UIColor = .black - fileprivate var mentionTapHandler: ((String) -> ())? - fileprivate var hashtagTapHandler: ((String) -> ())? - fileprivate var urlTapHandler: ((URL) -> ())? - fileprivate var customTapHandlers: [ActiveType : ((String) -> ())] = [:] + internal var mentionTapHandler: ((String) -> ())? + internal var hashtagTapHandler: ((String) -> ())? + internal var urlTapHandler: ((URL) -> ())? + internal var customTapHandlers: [ActiveType : ((String) -> ())] = [:] fileprivate var mentionFilterPredicate: ((String) -> Bool)? fileprivate var hashtagFilterPredicate: ((String) -> Bool)? fileprivate var selectedElement: ElementTuple? fileprivate var heightCorrection: CGFloat = 0 - fileprivate lazy var textStorage = NSTextStorage() + internal lazy var textStorage = NSTextStorage() fileprivate lazy var layoutManager = NSLayoutManager() fileprivate lazy var textContainer = NSTextContainer() lazy var activeElements = [ActiveType: [ElementTuple]]() - - // MARK: - internal (used for testing) - - // Needed for unit testing - internal var textStorageAttributedString: NSAttributedString { - return NSAttributedString(attributedString: textStorage) - } // MARK: - helper functions diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 2743ce4c..4737d6bb 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -243,7 +243,7 @@ class ActiveTypeTests: XCTestCase { // Find attributed text let range = (label.text! as NSString).range(of: "are") - let areText = label.textStorageAttributedString.attributedSubstring(from: range) + let areText = label.textStorage.attributedSubstring(from: range) // Enumber after attributes and find our font var foundCustomAttributedStyling = false @@ -254,6 +254,35 @@ class ActiveTypeTests: XCTestCase { XCTAssertTrue(foundCustomAttributedStyling) } + func testRemoveHandle() { + let newType1 = ActiveType.custom(pattern: "\\sare1\\b") + let newType2 = ActiveType.custom(pattern: "\\sare2\\b") + + label.handleMentionTap({_ in }) + label.handleHashtagTap({_ in }) + label.handleURLTap({_ in }) + label.handleCustomTap(for: newType1, handler: {_ in }) + label.handleCustomTap(for: newType2, handler: {_ in }) + + // Test some + XCTAssertNotNil(label.handleMentionTap) + XCTAssertNotNil(label.handleHashtagTap) + XCTAssertNotNil(label.handleURLTap) + XCTAssertEqual(label.customTapHandlers.count, 2) + + // Rest removal + label.removeHandle(for: .mention) + label.removeHandle(for: .hashtag) + label.removeHandle(for: .url) + label.removeHandle(for: newType1) + label.removeHandle(for: newType2) + + XCTAssertNil(label.mentionTapHandler) + XCTAssertNil(label.hashtagTapHandler) + XCTAssertNil(label.urlTapHandler) + XCTAssertEqual(label.customTapHandlers.count, 0) + + } func testFiltering() { label.text = "@user #tag" From ab5414bff916f6741cac2b5ddc60661349806d4b Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 25 Oct 2016 12:39:03 -0500 Subject: [PATCH 063/116] Split tests up --- ActiveLabelTests/ActiveTypeTests.swift | 45 +++++++++++++++----------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 4737d6bb..70a59e26 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -254,34 +254,43 @@ class ActiveTypeTests: XCTestCase { XCTAssertTrue(foundCustomAttributedStyling) } - func testRemoveHandle() { - let newType1 = ActiveType.custom(pattern: "\\sare1\\b") - let newType2 = ActiveType.custom(pattern: "\\sare2\\b") - + func testRemoveHandleMention() { label.handleMentionTap({_ in }) + XCTAssertNotNil(label.handleMentionTap) + + label.removeHandle(for: .mention) + XCTAssertNil(label.mentionTapHandler) + } + + func testRemoveHandleHashtag() { label.handleHashtagTap({_ in }) + XCTAssertNotNil(label.handleHashtagTap) + + label.removeHandle(for: .hashtag) + XCTAssertNil(label.hashtagTapHandler) + } + + func testRemoveHandleURL() { label.handleURLTap({_ in }) + XCTAssertNotNil(label.handleURLTap) + + label.removeHandle(for: .url) + XCTAssertNil(label.urlTapHandler) + } + + func testRemoveHandleCustom() { + let newType1 = ActiveType.custom(pattern: "\\sare1\\b") + let newType2 = ActiveType.custom(pattern: "\\sare2\\b") + label.handleCustomTap(for: newType1, handler: {_ in }) label.handleCustomTap(for: newType2, handler: {_ in }) - - // Test some - XCTAssertNotNil(label.handleMentionTap) - XCTAssertNotNil(label.handleHashtagTap) - XCTAssertNotNil(label.handleURLTap) XCTAssertEqual(label.customTapHandlers.count, 2) - // Rest removal - label.removeHandle(for: .mention) - label.removeHandle(for: .hashtag) - label.removeHandle(for: .url) label.removeHandle(for: newType1) - label.removeHandle(for: newType2) + XCTAssertEqual(label.customTapHandlers.count, 1) - XCTAssertNil(label.mentionTapHandler) - XCTAssertNil(label.hashtagTapHandler) - XCTAssertNil(label.urlTapHandler) + label.removeHandle(for: newType2) XCTAssertEqual(label.customTapHandlers.count, 0) - } func testFiltering() { From 64398613de2b19b07714020566464b034f625f62 Mon Sep 17 00:00:00 2001 From: DeMoN Date: Tue, 20 Dec 2016 21:16:36 -0300 Subject: [PATCH 064/116] feedback fixes --- ActiveLabel/ActiveLabel.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 765b8fcd..07d658bb 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -36,10 +36,10 @@ public protocol ActiveLabelDelegate: class { @IBInspectable public var URLSelectedColor: UIColor? { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var lineSpacing: Float = 0 { + @IBInspectable public var lineSpacing: CGFloat = 0 { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var minimumLineHeight: Float = 0 { + @IBInspectable public var minimumLineHeight: CGFloat = 0 { didSet { updateTextStorage(parseText: false) } } // MARK: - public methods @@ -307,8 +307,8 @@ public protocol ActiveLabelDelegate: class { let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.ByWordWrapping paragraphStyle.alignment = textAlignment - paragraphStyle.lineSpacing = CGFloat(lineSpacing) - paragraphStyle.minimumLineHeight = CGFloat(minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14) + paragraphStyle.lineSpacing = lineSpacing + paragraphStyle.minimumLineHeight = minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14 attributes[NSParagraphStyleAttributeName] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) From 446dbcd9fbc6403aa4434ad8d79eae765b8bff17 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Tue, 24 Jan 2017 09:54:46 +0100 Subject: [PATCH 065/116] Add tests to discard issue #150 --- ActiveLabelTests/ActiveTypeTests.swift | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 70a59e26..a728015a 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -11,7 +11,7 @@ import XCTest extension ActiveElement: Equatable {} -func ==(a: ActiveElement, b: ActiveElement) -> Bool { +public func ==(a: ActiveElement, b: ActiveElement) -> Bool { switch (a, b) { case (.mention(let a), .mention(let b)) where a == b: return true case (.hashtag(let a), .hashtag(let b)) where a == b: return true @@ -404,4 +404,33 @@ class ActiveTypeTests: XCTestCase { XCTAssertNotEqual(text.characters.count, label.text!.characters.count) } + + func testStringTrimmingURLShorterThanLimit() { + let text = "Tweet with short url: https://hello.co" + label.urlMaximumLength = 30 + label.text = text + + XCTAssertEqual(text, label.text!) + } + + func testStringTrimmingURLLongerThanLimit() { + let trimLimit = 30 + let url = "https://twitter.com/twicket_app/status/649678392372121601" + let trimmedURL = url.trim(to: trimLimit) + let text = "Tweet with long url: \(url)" + label.urlMaximumLength = trimLimit + label.text = text + + + XCTAssertNotEqual(text.characters.count, label.text!.characters.count) + + switch activeElements.first! { + case .url(let original, let trimmed): + XCTAssertEqual(original, url) + XCTAssertEqual(trimmed, trimmedURL) + default: + XCTAssert(false) + } + + } } From 5c030f3c37a43faaa5250130898028d7093c3d89 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Tue, 24 Jan 2017 10:14:41 +0100 Subject: [PATCH 066/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index d1f37dbb..0fd9cc5e 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.7.0' + s.version = '0.7.1' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From 3a095f04a5c8ed6b2b0a77479b4665413d6072f1 Mon Sep 17 00:00:00 2001 From: Pol Quintana Date: Thu, 14 Sep 2017 16:21:43 +0200 Subject: [PATCH 067/116] Update to Swift 4 --- ActiveLabel.xcodeproj/project.pbxproj | 35 ++++++++++++++----- .../xcschemes/ActiveLabel.xcscheme | 4 ++- ActiveLabel/ActiveLabel.swift | 30 ++++++++-------- ActiveLabel/StringTrimExtension.swift | 2 +- ActiveLabelDemo/ViewController.swift | 2 +- ActiveLabelTests/ActiveTypeTests.swift | 4 +-- 6 files changed, 49 insertions(+), 28 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 78bdd57a..22627713 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -235,12 +235,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = Optonaut; TargetAttributes = { 8F0249A11B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0800; + LastSwiftMigration = 0900; }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; @@ -248,6 +248,7 @@ }; 8F0249BF1B998A66005D8035 = { CreatedOnToolsVersion = 7.0; + LastSwiftMigration = 0900; }; }; }; @@ -371,14 +372,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -407,6 +414,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -421,14 +429,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -450,6 +464,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -474,7 +489,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -494,7 +510,8 @@ PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -505,7 +522,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -516,7 +533,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -530,7 +547,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -544,7 +562,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme index b5d72fd3..0185d6ea 100644 --- a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme +++ b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme @@ -1,6 +1,6 @@ ([String : Any]) +public typealias ConfigureLinkAttribute = (ActiveType, [NSAttributedStringKey : Any], Bool) -> ([NSAttributedStringKey : Any]) typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @IBDesignable open class ActiveLabel: UILabel { @@ -60,7 +60,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy @IBInspectable public var highlightFontName: String? = nil { didSet { updateTextStorage(parseText: false) } } - @IBInspectable public var highlightFontSize: CGFloat? = nil { + public var highlightFontSize: CGFloat? = nil { didSet { updateTextStorage(parseText: false) } } @@ -306,23 +306,23 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) - attributes[NSFontAttributeName] = font! - attributes[NSForegroundColorAttributeName] = textColor + attributes[NSAttributedStringKey.font] = font! + attributes[NSAttributedStringKey.foregroundColor] = textColor mutAttrString.addAttributes(attributes, range: range) - attributes[NSForegroundColorAttributeName] = mentionColor + attributes[NSAttributedStringKey.foregroundColor] = mentionColor for (type, elements) in activeElements { switch type { - case .mention: attributes[NSForegroundColorAttributeName] = mentionColor - case .hashtag: attributes[NSForegroundColorAttributeName] = hashtagColor - case .url: attributes[NSForegroundColorAttributeName] = URLColor - case .custom: attributes[NSForegroundColorAttributeName] = customColor[type] ?? defaultCustomColor + case .mention: attributes[NSAttributedStringKey.foregroundColor] = mentionColor + case .hashtag: attributes[NSAttributedStringKey.foregroundColor] = hashtagColor + case .url: attributes[NSAttributedStringKey.foregroundColor] = URLColor + case .custom: attributes[NSAttributedStringKey.foregroundColor] = customColor[type] ?? defaultCustomColor } if let highlightFont = hightlightFont { - attributes[NSFontAttributeName] = highlightFont + attributes[NSAttributedStringKey.font] = highlightFont } if let configureLinkAttribute = configureLinkAttribute { @@ -373,12 +373,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) - let paragraphStyle = attributes[NSParagraphStyleAttributeName] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() + let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = lineSpacing paragraphStyle.minimumLineHeight = minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14 - attributes[NSParagraphStyleAttributeName] = paragraphStyle + attributes[NSAttributedStringKey.paragraphStyle] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) return mutAttrString @@ -402,7 +402,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let possibleSelectedColor = customSelectedColor[selectedElement.type] ?? customColor[selectedElement.type] selectedColor = possibleSelectedColor ?? defaultCustomColor } - attributes[NSForegroundColorAttributeName] = selectedColor + attributes[NSAttributedStringKey.foregroundColor] = selectedColor } else { let unselectedColor: UIColor switch type { @@ -411,11 +411,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .url: unselectedColor = URLColor case .custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor } - attributes[NSForegroundColorAttributeName] = unselectedColor + attributes[NSAttributedStringKey.foregroundColor] = unselectedColor } if let highlightFont = hightlightFont { - attributes[NSFontAttributeName] = highlightFont + attributes[NSAttributedStringKey.font] = highlightFont } if let configureLinkAttribute = configureLinkAttribute { diff --git a/ActiveLabel/StringTrimExtension.swift b/ActiveLabel/StringTrimExtension.swift index 9bf3161c..9da442e9 100644 --- a/ActiveLabel/StringTrimExtension.swift +++ b/ActiveLabel/StringTrimExtension.swift @@ -11,6 +11,6 @@ import Foundation extension String { func trim(to maximumCharacters: Int) -> String { - return substring(to: index(startIndex, offsetBy: maximumCharacters)) + "..." + return "\(self[.. Date: Thu, 14 Sep 2017 16:22:20 +0200 Subject: [PATCH 068/116] Update Podfile to use Xcode 9 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 790372a3..9a7228a1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: objective-c -osx_image: xcode8 +osx_image: xcode9 before_install: - gem install scan - scan --version install: true script: -- scan -p "ActiveLabel.xcodeproj" -s "ActiveLabel" \ No newline at end of file +- scan -p "ActiveLabel.xcodeproj" -s "ActiveLabel" From 4bec2dae617cca42263b9b6efc84e8f4822cc4ad Mon Sep 17 00:00:00 2001 From: Sherwin Zadeh Date: Tue, 10 Oct 2017 11:44:28 -0700 Subject: [PATCH 069/116] Updated version for tag to use Swift 4 --- ActiveLabel.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 0fd9cc5e..f79c0ae2 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.7.1' + s.version = '0.8.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 3 + * Up-to-date: Swift 4 * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types From 75d8009ea5d991ae47acc81d0bc24ec19ee44ba5 Mon Sep 17 00:00:00 2001 From: Xuan Zhang Date: Mon, 27 Nov 2017 11:33:17 -0800 Subject: [PATCH 070/116] 'characters' Deprecation Fix 'characters' is deprecated: Please use String or Substring directly --- ActiveLabel/ActiveBuilder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 600abd25..1c75a5b8 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -34,7 +34,7 @@ struct ActiveBuilder { let word = nsstring.substring(with: match.range) .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - guard let maxLenght = maximumLenght, word.characters.count > maxLenght else { + guard let maxLenght = maximumLenght, word.count > maxLenght else { let range = maximumLenght == nil ? match.range : (text as NSString).range(of: word) let element = ActiveElement.create(with: type, text: word) elements.append((range, element, type)) From d0d7e467ad63c119dce857cccb0eec1ee6b5284a Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Wed, 10 Jan 2018 16:08:00 +0100 Subject: [PATCH 071/116] bump version to 0.8.1 --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index f79c0ae2..81a17536 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.8.0' + s.version = '0.8.1' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From fe07609e2feb3f0d83656a62c2508b0da6fa14c0 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Wed, 10 Jan 2018 17:05:28 +0100 Subject: [PATCH 072/116] update cocoapod build to swift 4.0 --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index 9f55b2cc..5186d070 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +4.0 From bf54d9d85b5a09a86ae2d98f9b3ccd4f27412f16 Mon Sep 17 00:00:00 2001 From: Lasha Efremidze Date: Wed, 10 Jan 2018 10:07:55 -0800 Subject: [PATCH 073/116] Update .swift-version --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index 9f55b2cc..5186d070 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 +4.0 From 955afe29964269058e92b1daeb5f22c85a08a58b Mon Sep 17 00:00:00 2001 From: Clineu Iansen Junior Date: Thu, 11 Jan 2018 19:37:03 -0200 Subject: [PATCH 074/116] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be669ffb..bb7225b9 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 3 +* Swift 4 * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types From 80a7d6be1ae449ccc84dfbc9af4f822955b320fa Mon Sep 17 00:00:00 2001 From: Sapozhnik Ivan Date: Wed, 31 Jan 2018 11:06:39 +0100 Subject: [PATCH 075/116] Fixed typo maxLenght --- ActiveLabel/ActiveBuilder.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 600abd25..eabb2c7e 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -34,14 +34,14 @@ struct ActiveBuilder { let word = nsstring.substring(with: match.range) .trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) - guard let maxLenght = maximumLenght, word.characters.count > maxLenght else { + guard let maxLength = maximumLenght, word.characters.count > maxLength else { let range = maximumLenght == nil ? match.range : (text as NSString).range(of: word) let element = ActiveElement.create(with: type, text: word) elements.append((range, element, type)) continue } - let trimmedWord = word.trim(to: maxLenght) + let trimmedWord = word.trim(to: maxLength) text = text.replacingOccurrences(of: word, with: trimmedWord) let newRange = (text as NSString).range(of: trimmedWord) From 78e33d04663d865419043d543ad677d53de7a1e2 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Mon, 11 Jun 2018 15:59:18 +0200 Subject: [PATCH 076/116] Update podspec to 0.9.0 --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 81a17536..a71f8914 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.8.1' + s.version = '0.9.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From cf7818aa9a151b218684b72df655487ffc6e8e78 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 8 Jul 2018 18:06:52 +0200 Subject: [PATCH 077/116] Update Custom types example This issue was raised by @ov1d1u: https://github.com/optonaut/ActiveLabel.swift/issues/224 --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb7225b9..3eee3f09 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ label.handleHashtagTap { hashtag in ```swift let customType = ActiveType.custom(pattern: "\\swith\\b") //Regex that looks for "with" label.enabledTypes = [.mention, .hashtag, .url, customType] - + label.text = "This is a post with #hashtags and a @userhandle." + label.customColor[customType] = UIColor.purple label.customSelectedColor[customType] = UIColor.green From f1d79f9f1600a3aa3e41ee259c0ace694d1f4f69 Mon Sep 17 00:00:00 2001 From: Cruz Date: Thu, 20 Sep 2018 21:16:33 +0900 Subject: [PATCH 078/116] Align README.md swift codes --- README.md | 53 ++++++++++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 3eee3f09..93beae9d 100644 --- a/README.md +++ b/README.md @@ -21,29 +21,27 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// import ActiveLabel let label = ActiveLabel() - label.numberOfLines = 0 label.enabledTypes = [.mention, .hashtag, .url] label.text = "This is a post with #hashtags and a @userhandle." label.textColor = .black label.handleHashtagTap { hashtag in - print("Success. You just tapped the \(hashtag) hashtag") + print("Success. You just tapped the \(hashtag) hashtag") } ``` ## Custom types ```swift - let customType = ActiveType.custom(pattern: "\\swith\\b") //Regex that looks for "with" - label.enabledTypes = [.mention, .hashtag, .url, customType] - label.text = "This is a post with #hashtags and a @userhandle." - - label.customColor[customType] = UIColor.purple - label.customSelectedColor[customType] = UIColor.green +let customType = ActiveType.custom(pattern: "\\swith\\b") //Regex that looks for "with" +label.enabledTypes = [.mention, .hashtag, .url, customType] +label.text = "This is a post with #hashtags and a @userhandle." +label.customColor[customType] = UIColor.purple +label.customSelectedColor[customType] = UIColor.green - label.handleCustomTap(for: customType) { element in - print("Custom type tapped: \(element)") - } +label.handleCustomTap(for: customType) { element in + print("Custom type tapped: \(element)") +} ``` ## Enable/disable highlighting @@ -51,7 +49,7 @@ label.handleHashtagTap { hashtag in By default, an ActiveLabel instance has the following configuration ```swift - label.enabledTypes = [.mention, .hashtag, .url] +label.enabledTypes = [.mention, .hashtag, .url] ``` But feel free to enable/disable to fit your requirements @@ -66,27 +64,24 @@ When using `customize(block:)`, you can group all the customizations on the labe Example: ```swift - - label.customize { label in - label.text = "This is a post with #multiple #hashtags and a @userhandle." - label.textColor = UIColor(red: 102.0/255, green: 117.0/255, blue: 127.0/255, alpha: 1) - label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) - label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) - label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) - label.handleMentionTap { self.alert("Mention", message: $0) } - label.handleHashtagTap { self.alert("Hashtag", message: $0) } - label.handleURLTap { self.alert("URL", message: $0.absoluteString) } - } - - +label.customize { label in + label.text = "This is a post with #multiple #hashtags and a @userhandle." + label.textColor = UIColor(red: 102.0/255, green: 117.0/255, blue: 127.0/255, alpha: 1) + label.hashtagColor = UIColor(red: 85.0/255, green: 172.0/255, blue: 238.0/255, alpha: 1) + label.mentionColor = UIColor(red: 238.0/255, green: 85.0/255, blue: 96.0/255, alpha: 1) + label.URLColor = UIColor(red: 85.0/255, green: 238.0/255, blue: 151.0/255, alpha: 1) + label.handleMentionTap { self.alert("Mention", message: $0) } + label.handleHashtagTap { self.alert("Hashtag", message: $0) } + label.handleURLTap { self.alert("URL", message: $0.absoluteString) } +} ``` ## Trim long urls You have the possiblity to set the maximum lenght a url can have; -``` - label.urlMaximumLength = 30 +```swift +label.urlMaximumLength = 30 ``` From now on, a url that's bigger than that, will be trimmed. @@ -101,8 +96,8 @@ From now on, a url that's bigger than that, will be trimmed. ##### `hashtagSelectedColor: UIColor?` ##### `URLColor: UIColor = .blueColor()` ##### `URLSelectedColor: UIColor?` -#### `customColor: [ActiveType : UIColor]` -#### `customSelectedColor: [ActiveType : UIColor]` +##### `customColor: [ActiveType : UIColor]` +##### `customSelectedColor: [ActiveType : UIColor]` ##### `lineSpacing: Float?` ##### `handleMentionTap: (String) -> ()` From d52c88bfdf1b67479a4b448f9f5b976d30f2bdc4 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Tue, 9 Oct 2018 10:49:33 +0200 Subject: [PATCH 079/116] Update project metadata --- ActiveLabel.xcodeproj/project.pbxproj | 9 ++++++++- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../xcshareddata/xcschemes/ActiveLabel.xcscheme | 4 +--- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 ActiveLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 22627713..ea3b08dd 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -235,7 +235,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = Optonaut; TargetAttributes = { 8F0249A11B9989B1005D8035 = { @@ -244,6 +244,7 @@ }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = G9KGF559KJ; LastSwiftMigration = 0800; }; 8F0249BF1B998A66005D8035 = { @@ -376,12 +377,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -433,12 +436,14 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -518,6 +523,7 @@ 8F0249BA1B9989B1005D8035 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = G9KGF559KJ; INFOPLIST_FILE = ActiveLabelTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; @@ -529,6 +535,7 @@ 8F0249BB1B9989B1005D8035 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + DEVELOPMENT_TEAM = G9KGF559KJ; INFOPLIST_FILE = ActiveLabelTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; diff --git a/ActiveLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ActiveLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/ActiveLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme index 0185d6ea..c8e3c084 100644 --- a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme +++ b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme @@ -1,6 +1,6 @@ Date: Tue, 9 Oct 2018 11:00:36 +0200 Subject: [PATCH 080/116] Update travis config --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9a7228a1..61776311 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode9 +osx_image: xcode10 before_install: - gem install scan - scan --version From 0642119ca017a6fdb31171da17cac4ee6ead14c7 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Tue, 9 Oct 2018 11:03:13 +0200 Subject: [PATCH 081/116] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93beae9d..439211f7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 4 +* Swift 4 (0.9.0) and 4.2 (1.0.0) * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types From c739832b4c8f860952e9bddc7c1455b1272fece0 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Tue, 9 Oct 2018 11:05:44 +0200 Subject: [PATCH 082/116] Update to Swift 4.2 and Xcode 10 --- ActiveLabel.xcodeproj/project.pbxproj | 14 ++++++------- ActiveLabel/ActiveLabel.swift | 28 +++++++++++++------------- ActiveLabelTests/ActiveTypeTests.swift | 10 ++++----- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index ea3b08dd..72a19acd 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -240,12 +240,12 @@ TargetAttributes = { 8F0249A11B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1000; }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = G9KGF559KJ; - LastSwiftMigration = 0800; + LastSwiftMigration = 1000; }; 8F0249BF1B998A66005D8035 = { CreatedOnToolsVersion = 7.0; @@ -494,8 +494,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -515,8 +514,7 @@ PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; @@ -528,7 +526,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -540,7 +538,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index d586ea17..b064cb52 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -13,7 +13,7 @@ public protocol ActiveLabelDelegate: class { func didSelect(_ text: String, type: ActiveType) } -public typealias ConfigureLinkAttribute = (ActiveType, [NSAttributedStringKey : Any], Bool) -> ([NSAttributedStringKey : Any]) +public typealias ConfigureLinkAttribute = (ActiveType, [NSAttributedString.Key : Any], Bool) -> ([NSAttributedString.Key : Any]) typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveType) @IBDesignable open class ActiveLabel: UILabel { @@ -306,23 +306,23 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) - attributes[NSAttributedStringKey.font] = font! - attributes[NSAttributedStringKey.foregroundColor] = textColor + attributes[NSAttributedString.Key.font] = font! + attributes[NSAttributedString.Key.foregroundColor] = textColor mutAttrString.addAttributes(attributes, range: range) - attributes[NSAttributedStringKey.foregroundColor] = mentionColor + attributes[NSAttributedString.Key.foregroundColor] = mentionColor for (type, elements) in activeElements { switch type { - case .mention: attributes[NSAttributedStringKey.foregroundColor] = mentionColor - case .hashtag: attributes[NSAttributedStringKey.foregroundColor] = hashtagColor - case .url: attributes[NSAttributedStringKey.foregroundColor] = URLColor - case .custom: attributes[NSAttributedStringKey.foregroundColor] = customColor[type] ?? defaultCustomColor + case .mention: attributes[NSAttributedString.Key.foregroundColor] = mentionColor + case .hashtag: attributes[NSAttributedString.Key.foregroundColor] = hashtagColor + case .url: attributes[NSAttributedString.Key.foregroundColor] = URLColor + case .custom: attributes[NSAttributedString.Key.foregroundColor] = customColor[type] ?? defaultCustomColor } if let highlightFont = hightlightFont { - attributes[NSAttributedStringKey.font] = highlightFont + attributes[NSAttributedString.Key.font] = highlightFont } if let configureLinkAttribute = configureLinkAttribute { @@ -373,12 +373,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) - let paragraphStyle = attributes[NSAttributedStringKey.paragraphStyle] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() + let paragraphStyle = attributes[NSAttributedString.Key.paragraphStyle] as? NSMutableParagraphStyle ?? NSMutableParagraphStyle() paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping paragraphStyle.alignment = textAlignment paragraphStyle.lineSpacing = lineSpacing paragraphStyle.minimumLineHeight = minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14 - attributes[NSAttributedStringKey.paragraphStyle] = paragraphStyle + attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) return mutAttrString @@ -402,7 +402,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let possibleSelectedColor = customSelectedColor[selectedElement.type] ?? customColor[selectedElement.type] selectedColor = possibleSelectedColor ?? defaultCustomColor } - attributes[NSAttributedStringKey.foregroundColor] = selectedColor + attributes[NSAttributedString.Key.foregroundColor] = selectedColor } else { let unselectedColor: UIColor switch type { @@ -411,11 +411,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .url: unselectedColor = URLColor case .custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor } - attributes[NSAttributedStringKey.foregroundColor] = unselectedColor + attributes[NSAttributedString.Key.foregroundColor] = unselectedColor } if let highlightFont = hightlightFont { - attributes[NSAttributedStringKey.font] = highlightFont + attributes[NSAttributedString.Key.font] = highlightFont } if let configureLinkAttribute = configureLinkAttribute { diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index 4ce5af04..d18ce7ac 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -27,7 +27,7 @@ class ActiveTypeTests: XCTestCase { let customEmptyType = ActiveType.custom(pattern: "") var activeElements: [ActiveElement] { - return label.activeElements.flatMap({$0.1.flatMap({$0.element})}) + return label.activeElements.flatMap({$0.1.compactMap({$0.element})}) } var currentElementString: String? { @@ -233,7 +233,7 @@ class ActiveTypeTests: XCTestCase { label.configureLinkAttribute = { type, attributes, isSelected in var atts = attributes if case newType = type { - atts[NSAttributedStringKey.font] = UIFont.boldSystemFont(ofSize: 14) + atts[NSAttributedString.Key.font] = UIFont.boldSystemFont(ofSize: 14) } return atts @@ -248,7 +248,7 @@ class ActiveTypeTests: XCTestCase { // Enumber after attributes and find our font var foundCustomAttributedStyling = false areText.enumerateAttributes(in: NSRange(location: 0, length: areText.length), options: [.longestEffectiveRangeNotRequired], using: { (attributes, range, stop) in - foundCustomAttributedStyling = attributes[NSAttributedStringKey.font] as? UIFont == UIFont.boldSystemFont(ofSize: 14) + foundCustomAttributedStyling = attributes[NSAttributedString.Key.font] as? UIFont == UIFont.boldSystemFont(ofSize: 14) }) XCTAssertTrue(foundCustomAttributedStyling) @@ -402,7 +402,7 @@ class ActiveTypeTests: XCTestCase { label.urlMaximumLength = 30 label.text = text - XCTAssertNotEqual(text.characters.count, label.text!.characters.count) + XCTAssertNotEqual(text.count, label.text!.count) } func testStringTrimmingURLShorterThanLimit() { @@ -422,7 +422,7 @@ class ActiveTypeTests: XCTestCase { label.text = text - XCTAssertNotEqual(text.characters.count, label.text!.characters.count) + XCTAssertNotEqual(text.count, label.text!.count) switch activeElements.first! { case .url(let original, let trimmed): From 9cc680aa35880320b48d731b07f0abad7aec302d Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Tue, 9 Oct 2018 11:17:18 +0200 Subject: [PATCH 083/116] Update .swift-version --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index 5186d070..bf77d549 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.0 +4.2 From 1b2597dea933ea6c18dd4d69e31e29706131f719 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Tue, 9 Oct 2018 11:25:14 +0200 Subject: [PATCH 084/116] Update .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 61776311..5a237c1f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: objective-c osx_image: xcode10 before_install: +- gem install bundler +- gem update bundler - gem install scan - scan --version install: true From fb71483cd571fe244bbba7f37627c1924a268867 Mon Sep 17 00:00:00 2001 From: Maziyar PANAHI Date: Tue, 9 Oct 2018 11:32:48 +0200 Subject: [PATCH 085/116] Update travis with fastlane --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5a237c1f..6c000437 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,7 @@ osx_image: xcode10 before_install: - gem install bundler - gem update bundler -- gem install scan -- scan --version +- gem install fastlane install: true script: -- scan -p "ActiveLabel.xcodeproj" -s "ActiveLabel" +- fastlane scan -p "ActiveLabel.xcodeproj" -s "ActiveLabel" From 4f2ee2690912493dc1453a14d49bbde664a1f520 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Tue, 9 Oct 2018 11:52:44 +0200 Subject: [PATCH 086/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index a71f8914..4a0a270d 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '0.9.0' + s.version = '1.0.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' From 7476ffe0e17fc6113816a5199fc939d14c4ade5b Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Tue, 9 Oct 2018 11:56:57 +0200 Subject: [PATCH 087/116] Update podspec for Swift 4.2 and Xcode 10 --- ActiveLabel.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 4a0a270d..ff419abf 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '1.0.0' + s.version = '1.0.1' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' @@ -12,7 +12,7 @@ Pod::Spec.new do |s| UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 4 + * Up-to-date: Swift 4.2 and Xcode 10 * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types From 2463840022b8031b4b2fe70f9c2083d178373cbc Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Tue, 9 Oct 2018 12:00:54 +0200 Subject: [PATCH 088/116] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 439211f7..2b57109a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 4 (0.9.0) and 4.2 (1.0.0) +* Swift 4.2 (+1.0) and 4.0 (0.9) * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types From c33e4b1fa982ded2fc58bbf6df6ec5ba5d462c42 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Fri, 23 Nov 2018 12:14:32 +0100 Subject: [PATCH 089/116] Create stale.yml Making stale issues explicit: "if work is not progressing, then it's stale. A comment is all it takes to keep the conversation alive." --- .github/stale.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..0867de02 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,18 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 180 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - security + - neverstale +# Label to use when marking an issue as stale +staleLabel: wontfix +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false From a4592876826608f545f53493c4e24ee4f6329c6d Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Fri, 23 Nov 2018 23:16:07 +0100 Subject: [PATCH 090/116] Change label to stale instead of wontfix --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index 0867de02..45c06d10 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -8,7 +8,7 @@ exemptLabels: - security - neverstale # Label to use when marking an issue as stale -staleLabel: wontfix +staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had From ab0f6e8e17e08cbfce257958e9677ed9ab15255b Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Fri, 23 Nov 2018 23:16:48 +0100 Subject: [PATCH 091/116] Update stale.yml --- .github/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/stale.yml b/.github/stale.yml index 45c06d10..2a9926bf 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,7 +1,7 @@ # Number of days of inactivity before an issue becomes stale daysUntilStale: 180 # Number of days of inactivity before a stale issue is closed -daysUntilClose: 7 +daysUntilClose: 3 # Issues with these labels will never be considered stale exemptLabels: - pinned From c8083c5cec28a1690e91eed2919ec0d9e4ea2376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cihat=20G=C3=BCnd=C3=BCz?= Date: Wed, 13 Mar 2019 10:56:33 +0100 Subject: [PATCH 092/116] Add basic SwiftPM manifest file --- Package.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Package.swift diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..84fde9d5 --- /dev/null +++ b/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version:4.2 +import PackageDescription + +let package = Package( + name: "ActiveLabel", + products: [ + .library(name: "ActiveLabel", targets: ["ActiveLabel"]) + ], + targets: [ + .target( + name: "ActiveLabel", + path: "ActiveLabel" + ) + ] +) From bafbeadf4b219a133b772b710bd12102bd2f51d2 Mon Sep 17 00:00:00 2001 From: Ruslana Kotova Date: Fri, 22 Mar 2019 22:46:29 +0500 Subject: [PATCH 093/116] Issue #312: Fixed intrinsicContentSize size calculation --- ActiveLabel/ActiveLabel.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index b064cb52..c3a49685 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -181,8 +181,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - Auto layout open override var intrinsicContentSize: CGSize { - let superSize = super.intrinsicContentSize - textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) + textContainer.size = CGSize(width: self.preferredMaxLayoutWidth, height: CGFloat.greatestFiniteMagnitude) let size = layoutManager.usedRect(for: textContainer) return CGSize(width: ceil(size.width), height: ceil(size.height)) } From 1005db4cc339ae761b0e58a87d8d1d28eb0c3a15 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 7 Apr 2019 11:33:53 +0200 Subject: [PATCH 094/116] Release candidate for Swift 5.0 --- .swift-version | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.swift-version b/.swift-version index bf77d549..819e07a2 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.2 +5.0 diff --git a/README.md b/README.md index 2b57109a..af87765f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 4.2 (+1.0) and 4.0 (0.9) +* Swift 5.0 (1.1.0) and 4.2 (1.0.1) * Default support for **Hashtags, Mentions, Links** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types @@ -151,7 +151,7 @@ github "optonaut/ActiveLabel.swift" CocoaPods 0.36 adds supports for Swift and embedded frameworks. To integrate ActiveLabel into your project add the following to your `Podfile`: ```ruby -platform :ios, '8.0' +platform :ios, '10.0' use_frameworks! pod 'ActiveLabel' From 0afc63bc9f5c83833078553ede0198fdfaf5a19f Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 19:50:12 +0200 Subject: [PATCH 095/116] Bump version and remove iOS 8 support --- ActiveLabel.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index ff419abf..13599f0c 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,18 +1,18 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '1.0.1' + s.version = '1.1.0' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' s.license = { :type => 'MIT', :file => 'LICENSE' } - s.platform = :ios, '8.0' + s.platform = :ios, '10.0' s.source = { :git => 'https://github.com/optonaut/ActiveLabel.swift.git', :tag => s.version.to_s } s.summary = 'UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift' s.description = <<-DESC UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 4.2 and Xcode 10 + * Up-to-date: Swift 5.0 and Xcode 10 * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types From b022d6d4ad401611a9527c8b19e6b353aa3e3413 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 19:50:21 +0200 Subject: [PATCH 096/116] Update project --- ActiveLabel.xcodeproj/project.pbxproj | 26 ++++++++++--------- .../xcschemes/ActiveLabel.xcscheme | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 72a19acd..6858d53c 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -235,17 +235,17 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 1000; + LastUpgradeCheck = 1020; ORGANIZATIONNAME = Optonaut; TargetAttributes = { 8F0249A11B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; 8F0249AB1B9989B1005D8035 = { CreatedOnToolsVersion = 7.0; DevelopmentTeam = G9KGF559KJ; - LastSwiftMigration = 1000; + LastSwiftMigration = 1020; }; 8F0249BF1B998A66005D8035 = { CreatedOnToolsVersion = 7.0; @@ -255,7 +255,7 @@ }; buildConfigurationList = 8F02499C1B9989B1005D8035 /* Build configuration list for PBXProject "ActiveLabel" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -369,6 +369,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -412,7 +413,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -428,6 +429,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -465,7 +467,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; @@ -488,13 +490,13 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ActiveLabel/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -509,12 +511,12 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = ActiveLabel/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -526,7 +528,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -538,7 +540,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme index c8e3c084..5a813f1b 100644 --- a/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme +++ b/ActiveLabel.xcodeproj/xcshareddata/xcschemes/ActiveLabel.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 11 Apr 2019 19:50:28 +0200 Subject: [PATCH 097/116] Update Info.plist --- ActiveLabel/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel/Info.plist b/ActiveLabel/Info.plist index d3de8eef..a6f720ec 100644 --- a/ActiveLabel/Info.plist +++ b/ActiveLabel/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 1.1 CFBundleSignature ???? CFBundleVersion From dd221b8c1113c8d460863a5729d2964a5799397f Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 19:54:21 +0200 Subject: [PATCH 098/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 13599f0c..753e6a71 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 5.0 and Xcode 10 + * Up-to-date: Swift 5.0 and Xcode 10.2 * Default support for Hashtags, Mentions, Links * Support for custom types via regex * Ability to enable highlighting only for the desired types From af93c76ff9eaf327cacef459def0338d85dbc133 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 19:58:32 +0200 Subject: [PATCH 099/116] Update ActiveLabel.swift --- ActiveLabel/ActiveLabel.swift | 136 +++++++++++++++++----------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index b064cb52..01861adf 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -20,13 +20,13 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - public properties open weak var delegate: ActiveLabelDelegate? - + open var enabledTypes: [ActiveType] = [.mention, .hashtag, .url] - + open var urlMaximumLength: Int? open var configureLinkAttribute: ConfigureLinkAttribute? - + @IBInspectable open var mentionColor: UIColor = .blue { didSet { updateTextStorage(parseText: false) } } @@ -69,12 +69,12 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy guard let highlightFontName = highlightFontName, let highlightFontSize = highlightFontSize else { return nil } return UIFont(name: highlightFontName, size: highlightFontSize) } - + // MARK: - public methods open func handleMentionTap(_ handler: @escaping (String) -> ()) { mentionTapHandler = handler } - + open func handleHashtagTap(_ handler: @escaping (String) -> ()) { hashtagTapHandler = handler } @@ -82,11 +82,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy open func handleURLTap(_ handler: @escaping (URL) -> ()) { urlTapHandler = handler } - + open func handleCustomTap(for type: ActiveType, handler: @escaping (String) -> ()) { customTapHandlers[type] = handler } - + open func removeHandle(for type: ActiveType) { switch type { case .hashtag: @@ -99,22 +99,22 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy customTapHandlers[type] = nil } } - + open func filterMention(_ predicate: @escaping (String) -> Bool) { mentionFilterPredicate = predicate updateTextStorage() } - + open func filterHashtag(_ predicate: @escaping (String) -> Bool) { hashtagFilterPredicate = predicate updateTextStorage() } - + // MARK: - override UILabel properties override open var text: String? { didSet { updateTextStorage() } } - + override open var attributedText: NSAttributedString? { didSet { updateTextStorage() } } @@ -130,44 +130,44 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy override open var textAlignment: NSTextAlignment { didSet { updateTextStorage(parseText: false)} } - + open override var numberOfLines: Int { didSet { textContainer.maximumNumberOfLines = numberOfLines } } - + open override var lineBreakMode: NSLineBreakMode { didSet { textContainer.lineBreakMode = lineBreakMode } } - + // MARK: - init functions override public init(frame: CGRect) { super.init(frame: frame) _customizing = false setupLabel() } - + required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) _customizing = false setupLabel() } - + open override func awakeFromNib() { super.awakeFromNib() updateTextStorage() } - + open override func drawText(in rect: CGRect) { let range = NSRange(location: 0, length: textStorage.length) - + textContainer.size = rect.size let newOrigin = textOrigin(inRect: rect) - + layoutManager.drawBackground(forGlyphRange: range, at: newOrigin) layoutManager.drawGlyphs(forGlyphRange: range, at: newOrigin) } - - + + // MARK: - customzation @discardableResult open func customize(_ block: (_ label: ActiveLabel) -> ()) -> ActiveLabel { @@ -177,21 +177,21 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy updateTextStorage() return self } - + // MARK: - Auto layout - + open override var intrinsicContentSize: CGSize { let superSize = super.intrinsicContentSize textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) let size = layoutManager.usedRect(for: textContainer) return CGSize(width: ceil(size.width), height: ceil(size.height)) } - + // MARK: - touch events func onTouch(_ touch: UITouch) -> Bool { let location = touch.location(in: self) var avoidSuperCall = false - + switch touch.phase { case .began, .moved: if let element = element(at: location) { @@ -207,7 +207,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } case .ended: guard let selectedElement = selectedElement else { return avoidSuperCall } - + switch selectedElement.element { case .mention(let userHandle): didTapMention(userHandle) case .hashtag(let hashtag): didTapHashtag(hashtag) @@ -226,11 +226,13 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy selectedElement = nil case .stationary: break + @unknown default: + break } - + return avoidSuperCall } - + // MARK: - private properties fileprivate var _customizing: Bool = true fileprivate var defaultCustomColor: UIColor = .black @@ -242,14 +244,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy fileprivate var mentionFilterPredicate: ((String) -> Bool)? fileprivate var hashtagFilterPredicate: ((String) -> Bool)? - + fileprivate var selectedElement: ElementTuple? fileprivate var heightCorrection: CGFloat = 0 internal lazy var textStorage = NSTextStorage() fileprivate lazy var layoutManager = NSLayoutManager() fileprivate lazy var textContainer = NSTextContainer() lazy var activeElements = [ActiveType: [ElementTuple]]() - + // MARK: - helper functions fileprivate func setupLabel() { @@ -260,7 +262,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy textContainer.maximumNumberOfLines = numberOfLines isUserInteractionEnabled = true } - + fileprivate func updateTextStorage(parseText: Bool = true) { if _customizing { return } // clean up previous active elements @@ -270,15 +272,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy setNeedsDisplay() return } - + let mutAttrString = addLineBreak(attributedText) - + if parseText { clearActiveElements() let newString = parseTextAndExtractActiveElements(mutAttrString) mutAttrString.mutableString.setString(newString) } - + addLinkAttribute(mutAttrString) textStorage.setAttributedString(mutAttrString) _customizing = true @@ -286,21 +288,21 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy _customizing = false setNeedsDisplay() } - + fileprivate func clearActiveElements() { selectedElement = nil for (type, _) in activeElements { activeElements[type]?.removeAll() } } - + fileprivate func textOrigin(inRect rect: CGRect) -> CGPoint { let usedRect = layoutManager.usedRect(for: textContainer) heightCorrection = (rect.height - usedRect.height)/2 let glyphOriginY = heightCorrection > 0 ? rect.origin.y + heightCorrection : rect.origin.y return CGPoint(x: rect.origin.x, y: glyphOriginY) } - + /// add link attribute fileprivate func addLinkAttribute(_ mutAttrString: NSMutableAttributedString) { var range = NSRange(location: 0, length: 0) @@ -309,11 +311,11 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy attributes[NSAttributedString.Key.font] = font! attributes[NSAttributedString.Key.foregroundColor] = textColor mutAttrString.addAttributes(attributes, range: range) - + attributes[NSAttributedString.Key.foregroundColor] = mentionColor - + for (type, elements) in activeElements { - + switch type { case .mention: attributes[NSAttributedString.Key.foregroundColor] = mentionColor case .hashtag: attributes[NSAttributedString.Key.foregroundColor] = hashtagColor @@ -324,23 +326,23 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if let highlightFont = hightlightFont { attributes[NSAttributedString.Key.font] = highlightFont } - + if let configureLinkAttribute = configureLinkAttribute { attributes = configureLinkAttribute(type, attributes, false) } - + for element in elements { mutAttrString.setAttributes(attributes, range: element.range) } } } - + /// use regex check all link ranges fileprivate func parseTextAndExtractActiveElements(_ attrString: NSAttributedString) -> String { var textString = attrString.string var textLength = textString.utf16.count var textRange = NSRange(location: 0, length: textLength) - + if enabledTypes.contains(.url) { let tuple = ActiveBuilder.createURLElements(from: textString, range: textRange, maximumLength: urlMaximumLength) let urlElements = tuple.0 @@ -350,7 +352,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy textRange = NSRange(location: 0, length: textLength) activeElements[.url] = urlElements } - + for type in enabledTypes where type != .url { var filter: ((String) -> Bool)? = nil if type == .mention { @@ -361,15 +363,15 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy let hashtagElements = ActiveBuilder.createElements(type: type, from: textString, range: textRange, filterPredicate: filter) activeElements[type] = hashtagElements } - + return textString } - - + + /// add line break mode fileprivate func addLineBreak(_ attrString: NSAttributedString) -> NSMutableAttributedString { let mutAttrString = NSMutableAttributedString(attributedString: attrString) - + var range = NSRange(location: 0, length: 0) var attributes = mutAttrString.attributes(at: 0, effectiveRange: &range) @@ -380,10 +382,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy paragraphStyle.minimumLineHeight = minimumLineHeight > 0 ? minimumLineHeight: self.font.pointSize * 1.14 attributes[NSAttributedString.Key.paragraphStyle] = paragraphStyle mutAttrString.setAttributes(attributes, range: range) - + return mutAttrString } - + fileprivate func updateAttributesWhenSelected(_ isSelected: Bool) { guard let selectedElement = selectedElement else { return @@ -391,7 +393,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var attributes = textStorage.attributes(at: 0, effectiveRange: nil) let type = selectedElement.type - + if isSelected { let selectedColor: UIColor switch type { @@ -421,24 +423,24 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if let configureLinkAttribute = configureLinkAttribute { attributes = configureLinkAttribute(type, attributes, isSelected) } - + textStorage.addAttributes(attributes, range: selectedElement.range) - + setNeedsDisplay() } - + fileprivate func element(at location: CGPoint) -> ElementTuple? { guard textStorage.length > 0 else { return nil } - + var correctLocation = location correctLocation.y -= heightCorrection let boundingRect = layoutManager.boundingRect(forGlyphRange: NSRange(location: 0, length: textStorage.length), in: textContainer) guard boundingRect.contains(correctLocation) else { return nil } - + let index = layoutManager.glyphIndex(for: correctLocation, in: textContainer) for element in activeElements.map({ $0.1 }).joined() { @@ -446,24 +448,24 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy return element } } - + return nil } - - + + //MARK: - Handle UI Responder touches open override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesBegan(touches, with: event) } - + open override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } if onTouch(touch) { return } super.touchesMoved(touches, with: event) } - + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first else { return } _ = onTouch(touch) @@ -475,7 +477,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy if onTouch(touch) { return } super.touchesEnded(touches, with: event) } - + //MARK: - ActiveLabel handler fileprivate func didTapMention(_ username: String) { guard let mentionHandler = mentionTapHandler else { @@ -484,7 +486,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } mentionHandler(username) } - + fileprivate func didTapHashtag(_ hashtag: String) { guard let hashtagHandler = hashtagTapHandler else { delegate?.didSelect(hashtag, type: .hashtag) @@ -492,7 +494,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } hashtagHandler(hashtag) } - + fileprivate func didTapStringURL(_ stringURL: String) { guard let urlHandler = urlTapHandler, let url = URL(string: stringURL) else { delegate?.didSelect(stringURL, type: .url) @@ -500,7 +502,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } urlHandler(url) } - + fileprivate func didTap(_ element: String, for type: ActiveType) { guard let elementHandler = customTapHandlers[type] else { delegate?.didSelect(element, type: type) @@ -511,7 +513,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy } extension ActiveLabel: UIGestureRecognizerDelegate { - + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } From cfcd9921cee9bd36a54d093fe11d247fcc5f5197 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 19:58:34 +0200 Subject: [PATCH 100/116] Update ActiveType.swift --- ActiveLabel/ActiveType.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index e339cb81..dc329544 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -13,7 +13,7 @@ enum ActiveElement { case hashtag(String) case url(original: String, trimmed: String) case custom(String) - + static func create(with activeType: ActiveType, text: String) -> ActiveElement { switch activeType { case .mention: return mention(text) @@ -29,7 +29,7 @@ public enum ActiveType { case hashtag case url case custom(pattern: String) - + var pattern: String { switch self { case .mention: return RegexParser.mentionPattern @@ -41,12 +41,12 @@ public enum ActiveType { } extension ActiveType: Hashable, Equatable { - public var hashValue: Int { + public func hash(into hasher: inout Hasher) { switch self { - case .mention: return -1 - case .hashtag: return -2 - case .url: return -3 - case .custom(let regex): return regex.hashValue + case .mention: hasher.combine(-1) + case .hashtag: hasher.combine(-2) + case .url: hasher.combine(-3) + case .custom(let regex): hasher.combine(regex) } } } From b00d14f041fe9f33fefc6aa6c74d81dd730f27f6 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:10:00 +0200 Subject: [PATCH 101/116] Migrate to Swift 5.0 --- ActiveLabel.xcodeproj/project.pbxproj | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 6858d53c..1b612516 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -249,7 +249,7 @@ }; 8F0249BF1B998A66005D8035 = { CreatedOnToolsVersion = 7.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1020; }; }; }; @@ -550,12 +550,11 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = ActiveLabelDemo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -565,12 +564,11 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; INFOPLIST_FILE = ActiveLabelDemo/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabelDemo; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; From 2da28c62e923f0cd4ed1ccbbad00a9580d400c00 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:10:09 +0200 Subject: [PATCH 102/116] Update ViewController.swift --- ActiveLabelDemo/ViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabelDemo/ViewController.swift b/ActiveLabelDemo/ViewController.swift index 92e45916..ff472213 100644 --- a/ActiveLabelDemo/ViewController.swift +++ b/ActiveLabelDemo/ViewController.swift @@ -54,7 +54,7 @@ class ViewController: UIViewController { var atts = attributes switch type { case customType3: - atts[NSAttributedStringKey.font] = isSelected ? UIFont.boldSystemFont(ofSize: 16) : UIFont.boldSystemFont(ofSize: 14) + atts[NSAttributedString.Key.font] = isSelected ? UIFont.boldSystemFont(ofSize: 16) : UIFont.boldSystemFont(ofSize: 14) default: () } @@ -79,7 +79,7 @@ class ViewController: UIViewController { } func alert(_ title: String, message: String) { - let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) + let vc = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) vc.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil)) present(vc, animated: true, completion: nil) } From 7e5a93ef011bd6dee26413548d650b8cb7be5604 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:10:11 +0200 Subject: [PATCH 103/116] Update AppDelegate.swift --- ActiveLabelDemo/AppDelegate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ActiveLabelDemo/AppDelegate.swift b/ActiveLabelDemo/AppDelegate.swift index 73d7eb2e..4a109c87 100644 --- a/ActiveLabelDemo/AppDelegate.swift +++ b/ActiveLabelDemo/AppDelegate.swift @@ -13,7 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { return true } From 969a62e0a0be36372490e63d9c91fc9f409ef36f Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:15:43 +0200 Subject: [PATCH 104/116] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cbf3e072..d6246ddf 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ DerivedData # Carthage/Checkouts Carthage/Build +test_output/* \ No newline at end of file From 0573e8807e80b3b9af3de029a28705e6ebb680e7 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:15:48 +0200 Subject: [PATCH 105/116] Update project.pbxproj --- ActiveLabel.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 1b612516..7c722bd8 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -418,7 +418,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -471,7 +471,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; From 201f31eca33c1661261b995e6a4de79779b713f7 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Thu, 11 Apr 2019 20:16:30 +0200 Subject: [PATCH 106/116] Update travis to use Xcode 10.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6c000437..eff32e98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10 +osx_image: xcode10.2 before_install: - gem install bundler - gem update bundler From e709f23e0a032d572a3f105b538df5fa83768ce9 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Mon, 13 May 2019 18:14:35 +0200 Subject: [PATCH 107/116] Update minimum iOS version Bring the `Install` section to higher level. --- README.md | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index af87765f..56f8c4ab 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,28 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ![](ActiveLabelDemo/demo.gif) + +## Install (iOS 10+) + +### Carthage + +Add the following to your `Cartfile` and follow [these instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) + +``` +github "optonaut/ActiveLabel.swift" +``` + +### CocoaPods + +CocoaPods 0.36 adds supports for Swift and embedded frameworks. To integrate ActiveLabel into your project add the following to your `Podfile`: + +```ruby +platform :ios, '10.0' +use_frameworks! + +pod 'ActiveLabel' +``` + ## Usage ```swift @@ -136,27 +158,6 @@ label.filterHashtag { hashtag in validHashtags.contains(hashtag) } label.filterMention { mention in validMentions.contains(mention) } ``` -## Install (iOS 8+) - -### Carthage - -Add the following to your `Cartfile` and follow [these instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) - -``` -github "optonaut/ActiveLabel.swift" -``` - -### CocoaPods - -CocoaPods 0.36 adds supports for Swift and embedded frameworks. To integrate ActiveLabel into your project add the following to your `Podfile`: - -```ruby -platform :ios, '10.0' -use_frameworks! - -pod 'ActiveLabel' -``` - ## Alternatives Before writing `ActiveLabel` we've tried a lot of the following alternatives but weren't quite satisfied with the quality level or ease of usage, so we decided to contribute our own solution. From 69e25cb31d09c753a2d2f1300efb1011cd2ac97b Mon Sep 17 00:00:00 2001 From: "mehul.modi" Date: Tue, 11 Feb 2020 19:32:17 +0530 Subject: [PATCH 108/116] added email support --- ActiveLabel/ActiveBuilder.swift | 2 ++ ActiveLabel/ActiveLabel.swift | 19 +++++++++++++++++++ ActiveLabel/ActiveType.swift | 6 ++++++ ActiveLabel/RegexParser.swift | 1 + 4 files changed, 28 insertions(+) diff --git a/ActiveLabel/ActiveBuilder.swift b/ActiveLabel/ActiveBuilder.swift index 40971c8f..ed4916d6 100644 --- a/ActiveLabel/ActiveBuilder.swift +++ b/ActiveLabel/ActiveBuilder.swift @@ -20,6 +20,8 @@ struct ActiveBuilder { return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) case .custom: return createElements(from: text, for: type, range: range, minLength: 1, filterPredicate: filterPredicate) + case .email: + return createElements(from: text, for: type, range: range, filterPredicate: filterPredicate) } } diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 01861adf..4607c726 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -87,6 +87,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy customTapHandlers[type] = handler } + open func handleEmailTap(_ handler: @escaping (String) -> ()) { + emailTapHandler = handler + } + open func removeHandle(for type: ActiveType) { switch type { case .hashtag: @@ -97,6 +101,8 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy urlTapHandler = nil case .custom: customTapHandlers[type] = nil + case .email: + emailTapHandler = nil } } @@ -213,6 +219,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .hashtag(let hashtag): didTapHashtag(hashtag) case .url(let originalURL, _): didTapStringURL(originalURL) case .custom(let element): didTap(element, for: selectedElement.type) + case .email(let element): didTapStringEmail(element) } let when = DispatchTime.now() + Double(Int64(0.25 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) @@ -240,6 +247,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy internal var mentionTapHandler: ((String) -> ())? internal var hashtagTapHandler: ((String) -> ())? internal var urlTapHandler: ((URL) -> ())? + internal var emailTapHandler: ((String) -> ())? internal var customTapHandlers: [ActiveType : ((String) -> ())] = [:] fileprivate var mentionFilterPredicate: ((String) -> Bool)? @@ -321,6 +329,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .hashtag: attributes[NSAttributedString.Key.foregroundColor] = hashtagColor case .url: attributes[NSAttributedString.Key.foregroundColor] = URLColor case .custom: attributes[NSAttributedString.Key.foregroundColor] = customColor[type] ?? defaultCustomColor + case .email: attributes[NSAttributedString.Key.foregroundColor] = URLColor } if let highlightFont = hightlightFont { @@ -403,6 +412,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .custom: let possibleSelectedColor = customSelectedColor[selectedElement.type] ?? customColor[selectedElement.type] selectedColor = possibleSelectedColor ?? defaultCustomColor + case .email: selectedColor = URLSelectedColor ?? URLColor } attributes[NSAttributedString.Key.foregroundColor] = selectedColor } else { @@ -412,6 +422,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy case .hashtag: unselectedColor = hashtagColor case .url: unselectedColor = URLColor case .custom: unselectedColor = customColor[selectedElement.type] ?? defaultCustomColor + case .email: unselectedColor = URLColor } attributes[NSAttributedString.Key.foregroundColor] = unselectedColor } @@ -503,6 +514,14 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy urlHandler(url) } + fileprivate func didTapStringEmail(_ stringEmail: String) { + guard let emailHandler = emailTapHandler else { + delegate?.didSelect(stringEmail, type: .email) + return + } + emailHandler(stringEmail) + } + fileprivate func didTap(_ element: String, for type: ActiveType) { guard let elementHandler = customTapHandlers[type] else { delegate?.didSelect(element, type: type) diff --git a/ActiveLabel/ActiveType.swift b/ActiveLabel/ActiveType.swift index dc329544..55ee66fc 100644 --- a/ActiveLabel/ActiveType.swift +++ b/ActiveLabel/ActiveType.swift @@ -11,6 +11,7 @@ import Foundation enum ActiveElement { case mention(String) case hashtag(String) + case email(String) case url(original: String, trimmed: String) case custom(String) @@ -18,6 +19,7 @@ enum ActiveElement { switch activeType { case .mention: return mention(text) case .hashtag: return hashtag(text) + case .email: return email(text) case .url: return url(original: text, trimmed: text) case .custom: return custom(text) } @@ -28,6 +30,7 @@ public enum ActiveType { case mention case hashtag case url + case email case custom(pattern: String) var pattern: String { @@ -35,6 +38,7 @@ public enum ActiveType { case .mention: return RegexParser.mentionPattern case .hashtag: return RegexParser.hashtagPattern case .url: return RegexParser.urlPattern + case .email: return RegexParser.emailPattern case .custom(let regex): return regex } } @@ -46,6 +50,7 @@ extension ActiveType: Hashable, Equatable { case .mention: hasher.combine(-1) case .hashtag: hasher.combine(-2) case .url: hasher.combine(-3) + case .email: hasher.combine(-4) case .custom(let regex): hasher.combine(regex) } } @@ -56,6 +61,7 @@ public func ==(lhs: ActiveType, rhs: ActiveType) -> Bool { case (.mention, .mention): return true case (.hashtag, .hashtag): return true case (.url, .url): return true + case (.email, .email): return true case (.custom(let pattern1), .custom(let pattern2)): return pattern1 == pattern2 default: return false } diff --git a/ActiveLabel/RegexParser.swift b/ActiveLabel/RegexParser.swift index b0ad4a4b..fe9bd7ab 100644 --- a/ActiveLabel/RegexParser.swift +++ b/ActiveLabel/RegexParser.swift @@ -12,6 +12,7 @@ struct RegexParser { static let hashtagPattern = "(?:^|\\s|$)#[\\p{L}0-9_]*" static let mentionPattern = "(?:^|\\s|$|[.])@[\\p{L}0-9_]*" + static let emailPattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}" static let urlPattern = "(^|[\\s.:;?\\-\\]<\\(])" + "((https?://|www\\.|pic\\.)[-\\w;/?:@&=+$\\|\\_.!~*\\|'()\\[\\]%#,☺]+[\\w/#](\\(\\))?)" + "(?=$|[\\s',\\|\\(\\).:;?\\-\\[\\]>\\)])" From 2e3680c42aa981d87be71f50eb73da85e4aa9df0 Mon Sep 17 00:00:00 2001 From: "mehul.modi" Date: Tue, 11 Feb 2020 19:39:54 +0530 Subject: [PATCH 109/116] added email type to test cases --- ActiveLabelTests/ActiveTypeTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ActiveLabelTests/ActiveTypeTests.swift b/ActiveLabelTests/ActiveTypeTests.swift index d18ce7ac..55f1da8b 100644 --- a/ActiveLabelTests/ActiveTypeTests.swift +++ b/ActiveLabelTests/ActiveTypeTests.swift @@ -37,6 +37,7 @@ class ActiveTypeTests: XCTestCase { case .hashtag(let hashtag): return hashtag case .url(let url, _): return url case .custom(let element): return element + case .email(let element): return element } } @@ -47,6 +48,7 @@ class ActiveTypeTests: XCTestCase { case .hashtag: return .hashtag case .url: return .url case .custom: return customEmptyType + case .email: return .email } } From 6d1f638fafba6a40525a85061c1c85a41225b376 Mon Sep 17 00:00:00 2001 From: mehulmodihb <33020448+mehulmodihb@users.noreply.github.com> Date: Tue, 11 Feb 2020 20:00:55 +0530 Subject: [PATCH 110/116] Update README.md --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 56f8c4ab..d76503a6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # ActiveLabel.swift [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Build Status](https://travis-ci.org/optonaut/ActiveLabel.swift.svg)](https://travis-ci.org/optonaut/ActiveLabel.swift) -UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift +UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://), Emails and custom regex patterns, written in Swift ## Features * Swift 5.0 (1.1.0) and 4.2 (1.0.1) -* Default support for **Hashtags, Mentions, Links** +* Default support for **Hashtags, Mentions, Links, Emails** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types * Ability to trim urls @@ -44,7 +44,7 @@ import ActiveLabel let label = ActiveLabel() label.numberOfLines = 0 -label.enabledTypes = [.mention, .hashtag, .url] +label.enabledTypes = [.mention, .hashtag, .url, .email] label.text = "This is a post with #hashtags and a @userhandle." label.textColor = .black label.handleHashtagTap { hashtag in @@ -56,7 +56,7 @@ label.handleHashtagTap { hashtag in ```swift let customType = ActiveType.custom(pattern: "\\swith\\b") //Regex that looks for "with" -label.enabledTypes = [.mention, .hashtag, .url, customType] +label.enabledTypes = [.mention, .hashtag, .url, .email, customType] label.text = "This is a post with #hashtags and a @userhandle." label.customColor[customType] = UIColor.purple label.customSelectedColor[customType] = UIColor.green @@ -71,7 +71,7 @@ label.handleCustomTap(for: customType) { element in By default, an ActiveLabel instance has the following configuration ```swift -label.enabledTypes = [.mention, .hashtag, .url] +label.enabledTypes = [.mention, .hashtag, .url, .email] ``` But feel free to enable/disable to fit your requirements @@ -140,6 +140,12 @@ label.handleHashtagTap { hashtag in print("\(hashtag) tapped") } label.handleURLTap { url in UIApplication.shared.openURL(url) } ``` +##### `handleEmailTap: (String) -> ()` + +```swift +label.handleEmailTap { email in print("\(email) tapped") } +``` + ##### `handleCustomTap(for type: ActiveType, handler: (String) -> ())` ```swift From 6bbe600577857be3e331d48618c820640f5c6123 Mon Sep 17 00:00:00 2001 From: casperson Date: Thu, 26 Mar 2020 15:30:37 -0700 Subject: [PATCH 111/116] Added in missing cases for new UITouchPhases, iOS 13.4 --- .travis.yml | 2 +- ActiveLabel/ActiveLabel.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index eff32e98..5fe3d2c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode10.2 +osx_image: xcode11.4 before_install: - gem install bundler - gem update bundler diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 01861adf..d3e10629 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -193,7 +193,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy var avoidSuperCall = false switch touch.phase { - case .began, .moved: + case .began, .moved, .regionEntered, .regionMoved: if let element = element(at: location) { if element.range.location != selectedElement?.range.location || element.range.length != selectedElement?.range.length { updateAttributesWhenSelected(false) @@ -205,7 +205,7 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy updateAttributesWhenSelected(false) selectedElement = nil } - case .ended: + case .ended, .regionExited: guard let selectedElement = selectedElement else { return avoidSuperCall } switch selectedElement.element { From 4cdd88f1a300a0f6a33236bfe206bd6c851b57b7 Mon Sep 17 00:00:00 2001 From: Filipe Alvarenga Date: Sun, 28 Jun 2020 20:02:51 -0300 Subject: [PATCH 112/116] Adjust height to zero when there's no text set --- ActiveLabel/ActiveLabel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ActiveLabel/ActiveLabel.swift b/ActiveLabel/ActiveLabel.swift index 01861adf..25f2bd00 100644 --- a/ActiveLabel/ActiveLabel.swift +++ b/ActiveLabel/ActiveLabel.swift @@ -181,6 +181,10 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy // MARK: - Auto layout open override var intrinsicContentSize: CGSize { + guard let text = text, !text.isEmpty else { + return .zero + } + let superSize = super.intrinsicContentSize textContainer.size = CGSize(width: superSize.width, height: CGFloat.greatestFiniteMagnitude) let size = layoutManager.usedRect(for: textContainer) From 85136b17ef48575c086b333eeb6d36d53e81042e Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 15 Nov 2020 17:08:16 +0100 Subject: [PATCH 113/116] [skip travis] Update travis to Xcode 12.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5fe3d2c6..3dccd3ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: objective-c -osx_image: xcode11.4 +osx_image: xcode12.2 before_install: - gem install bundler - gem update bundler From b3bfb455e3ab032d0edb42bb85bfb8c3ae329a40 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 15 Nov 2020 17:08:29 +0100 Subject: [PATCH 114/116] Bump version to 1.1.5 --- ActiveLabel.podspec | 2 +- ActiveLabel.xcodeproj/project.pbxproj | 2 ++ ActiveLabel/Info.plist | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 753e6a71..53a60f36 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ActiveLabel' - s.version = '1.1.0' + s.version = '1.1.5' s.author = { 'Optonaut' => 'hello@optonaut.co' } s.homepage = 'https://github.com/optonaut/ActiveLabel.swift' diff --git a/ActiveLabel.xcodeproj/project.pbxproj b/ActiveLabel.xcodeproj/project.pbxproj index 7c722bd8..311e4a1c 100644 --- a/ActiveLabel.xcodeproj/project.pbxproj +++ b/ActiveLabel.xcodeproj/project.pbxproj @@ -492,6 +492,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; @@ -513,6 +514,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = optonaut.ActiveLabel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; diff --git a/ActiveLabel/Info.plist b/ActiveLabel/Info.plist index a6f720ec..ca23c84f 100644 --- a/ActiveLabel/Info.plist +++ b/ActiveLabel/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.1 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion From 726af7f08aa01c9e0b60096a33ff536b79a1ab4f Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 15 Nov 2020 17:10:42 +0100 Subject: [PATCH 115/116] Update README.md --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d76503a6..3d9795f3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ## Features -* Swift 5.0 (1.1.0) and 4.2 (1.0.1) +* Swift 5.0 (1.1.0+) and 4.2 (1.0.1) * Default support for **Hashtags, Mentions, Links, Emails** * Support for **custom types** via regex * Ability to enable highlighting only for the desired types @@ -15,14 +15,13 @@ UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http:// ![](ActiveLabelDemo/demo.gif) - ## Install (iOS 10+) ### Carthage Add the following to your `Cartfile` and follow [these instructions](https://github.com/Carthage/Carthage#adding-frameworks-to-an-application) -``` +```sh github "optonaut/ActiveLabel.swift" ``` @@ -60,9 +59,8 @@ label.enabledTypes = [.mention, .hashtag, .url, .email, customType] label.text = "This is a post with #hashtags and a @userhandle." label.customColor[customType] = UIColor.purple label.customSelectedColor[customType] = UIColor.green - -label.handleCustomTap(for: customType) { element in - print("Custom type tapped: \(element)") +label.handleCustomTap(for: customType) { element in + print("Custom type tapped: \(element)") } ``` @@ -76,7 +74,6 @@ label.enabledTypes = [.mention, .hashtag, .url, .email] But feel free to enable/disable to fit your requirements - ## Batched customization When using ActiveLabel, it is recommended to use the `customize(block:)` method to customize it. The reason is that ActiveLabel is reacting to each property that you set. So if you set 3 properties, the textContainer is refreshed 3 times. From c3ad1cf51f7a306099212e448827d96ce3423fc8 Mon Sep 17 00:00:00 2001 From: Maziyar Panahi Date: Sun, 15 Nov 2020 17:15:57 +0100 Subject: [PATCH 116/116] Update ActiveLabel.podspec --- ActiveLabel.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec index 53a60f36..56b7226a 100644 --- a/ActiveLabel.podspec +++ b/ActiveLabel.podspec @@ -12,8 +12,8 @@ Pod::Spec.new do |s| UILabel drop-in replacement supporting Hashtags (#), Mentions (@), URLs (http://) and custom regex patterns, written in Swift Features - * Up-to-date: Swift 5.0 and Xcode 10.2 - * Default support for Hashtags, Mentions, Links + * Swift 5.0 (1.1.0+) and 4.2 (1.0.1) + * Default support for **Hashtags, Mentions, Links, Emails** * Support for custom types via regex * Ability to enable highlighting only for the desired types * Ability to trim urls