Skip to content

Commit

Permalink
Merge pull request #370 from optonaut/115-release-candidate
Browse files Browse the repository at this point in the history
1.15 release candidate
  • Loading branch information
maziyarpanahi committed Nov 15, 2020
2 parents e709f23 + c3ad1cf commit 36949c1
Show file tree
Hide file tree
Showing 10 changed files with 59 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .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
Expand Down
6 changes: 3 additions & 3 deletions 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'
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ActiveLabel.xcodeproj/project.pbxproj
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions ActiveLabel/ActiveBuilder.swift
Expand Up @@ -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)
}
}

Expand Down
30 changes: 26 additions & 4 deletions ActiveLabel/ActiveLabel.swift
Expand Up @@ -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:
Expand All @@ -97,6 +101,8 @@ typealias ElementTuple = (range: NSRange, element: ActiveElement, type: ActiveTy
urlTapHandler = nil
case .custom:
customTapHandlers[type] = nil
case .email:
emailTapHandler = nil
}
}

Expand Down Expand Up @@ -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))
}
Expand All @@ -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)
Expand All @@ -205,14 +214,15 @@ 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 {
case .mention(let userHandle): didTapMention(userHandle)
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)
Expand Down Expand Up @@ -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)?
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 6 additions & 0 deletions ActiveLabel/ActiveType.swift
Expand Up @@ -11,13 +11,15 @@ import Foundation
enum ActiveElement {
case mention(String)
case hashtag(String)
case email(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 .email: return email(text)
case .url: return url(original: text, trimmed: text)
case .custom: return custom(text)
}
Expand All @@ -28,13 +30,15 @@ public enum ActiveType {
case mention
case hashtag
case url
case email
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 .email: return RegexParser.emailPattern
case .custom(let regex): return regex
}
}
Expand All @@ -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)
}
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion ActiveLabel/Info.plist
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.1</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
1 change: 1 addition & 0 deletions ActiveLabel/RegexParser.swift
Expand Up @@ -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',\\|\\(\\).:;?\\-\\[\\]>\\)])"
Expand Down
2 changes: 2 additions & 0 deletions ActiveLabelTests/ActiveTypeTests.swift
Expand Up @@ -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
}
}

Expand All @@ -47,6 +48,7 @@ class ActiveTypeTests: XCTestCase {
case .hashtag: return .hashtag
case .url: return .url
case .custom: return customEmptyType
case .email: return .email
}
}

Expand Down
27 changes: 15 additions & 12 deletions 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
Expand All @@ -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"
```

Expand All @@ -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
Expand All @@ -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)")
}
```

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 36949c1

Please sign in to comment.