diff --git a/.travis.yml b/.travis.yml
index eff32e98..3dccd3ab 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,5 @@
language: objective-c
-osx_image: xcode10.2
+osx_image: xcode12.2
before_install:
- gem install bundler
- gem update bundler
diff --git a/ActiveLabel.podspec b/ActiveLabel.podspec
index 753e6a71..56b7226a 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'
@@ -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
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/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..073d4532 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
}
}
@@ -181,8 +187,11 @@ 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)
+ guard let text = text, !text.isEmpty else {
+ return .zero
+ }
+
+ 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))
}
@@ -193,7 +202,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 +214,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 {
@@ -213,6 +222,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 +250,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 +332,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 +415,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 +425,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 +517,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/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
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',\\|\\(\\).:;?\\-\\[\\]>\\)])"
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
}
}
diff --git a/README.md b/README.md
index 56f8c4ab..3d9795f3 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**
+* 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
@@ -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"
```
@@ -44,7 +43,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,13 +55,12 @@ 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
-
-label.handleCustomTap(for: customType) { element in
- print("Custom type tapped: \(element)")
+label.handleCustomTap(for: customType) { element in
+ print("Custom type tapped: \(element)")
}
```
@@ -71,12 +69,11 @@ 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
-
## 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.
@@ -140,6 +137,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