Swift TTTAttributedLabel replacement

Overview

Nantes 🥕

CI Status CocoaPods Compatible Carthage Compatible

This library is a Swift port/fork of the popular Objective-C library TTTAttributedLabel. Much ❤️ and credit goes to Mattt for creating such a great UILabel replacement library.

Nantes is a pure-Swift UILabel replacement. It supports attributes, data detectors, and more. It also supports link embedding automatically and with NSTextCheckingTypes.

Come build awesome things with us here at Instacart!

Requirements

  • iOS 8.0+
  • Swift 4.2

Installation

Nantes is available through Carthage. To install it, add the following line to your Cartfile:

github "instacart/nantes"

CocoaPods

Nantes is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Nantes'

Communication

If you need help, feel free to open an issue. Please search before opening one, someone might have run into something similar.

Contributing

Opening a pull request is the best way to get something fixed. If you need help, feel free to open an issue, hopefully someone can help you out with a problem you're running into.

Author

chansen22, [email protected]

Example

To run the example project, clone the repo, and run pod install from the Example directory first.

Getting Started

Check out Nantes in the Example directory for more examples.

import Nantes

let label: NantesLabel = .init(frame: .zero)
label.attributedTruncationToken = NSAttributedString(string: "... more")
label.numberOfLines = 3
label.labelTappedBlock = {
  label.numberOfLines = label.numberOfLines == 0 ? 3 : 0 // Flip between limiting lines and not

  UIView.animateWithDuration(0.2, animations: {
    self.view.layoutIfNeeded()
  })
}

label.text = "Nantes label is great! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ac urna et ante lobortis varius. Nunc rhoncus enim vitae sem commodo sodales. Morbi id augue id augue finibus tincidunt. Cras ac massa nisi. Maecenas elementum vitae elit eu mattis. Duis pretium turpis ut justo accumsan molestie. Mauris elit elit, maximus eu risus sed, vestibulum sodales enim. Sed porttitor vestibulum tincidunt. Maecenas mollis tortor quam, sed porta justo rhoncus id. Phasellus vitae augue tempor, luctus metus sit amet, dictum urna. Morbi sit amet feugiat purus. Proin vitae finibus lectus, eu gravida erat."
view.addSubview(label)

let linkLabel: NantesLabel = .init(frame: .zero)
linkLabel.delegate = self // NantesLabelDelegate
linkLabel.linkAttributes = [NSAttributedString.Key.foregroundColor: UIColor.green]
linkLabel.text = "https://www.instacart.com"
view.addSubview(linkLabel)

// Link handling

func attributedLabel(_ label: NantesLabel, didSelectLink link: URL) {
  print("Tapped link: \(link)")
}

License

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Xcode 12 warning about iOS simulator deployment target.

    Xcode 12 warning about iOS simulator deployment target.

    Xcode 12 release is showing this warning

    The iOS Simulator deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 9.0 to 14.0.99.
    
    opened by Prince2k3 8
  • crash on Truncation.swift:57 let originalLine = lines[originalIndex]

    crash on Truncation.swift:57 let originalLine = lines[originalIndex]

    // numberOfLines = 0 // tokenLines.count = 1 // index = 0 // originalIndex = -1 Truncation.swift:49 let originalIndex = self.numberOfLines - tokenLines.count + index

    crash on Truncation.swift:57 let originalLine = lines[originalIndex]

    (lldb) po tokenLines
    ▿ 1 element
      - 0 : <CTLine: 0x6000034b0000>{run count = 1, string range = (0, 1), width = 13, A/D/L = 12.72/4.08/0, glyph count = 1, runs = (
    
    <CTRun: 0x7fe4fad6ffc0>{string range = (0, 1), string = "\u2026", attributes = {
        NSColor = "UIExtendedSRGBColorSpace 0.4 0.368627 1 1";
        NSFont = "<UICTFont: 0x7fe4fad2e020> font-family: \"PingFangSC-Regular\"; font-weight: normal; font-style: normal; font-size: 12.00pt";
        NSKern = 1;
        NSParagraphStyle = "Alignment 4, LineSpacing 1, ParagraphSpacing 0, ParagraphSpacingBefore 0, HeadIndent 0, TailIndent 0, FirstLineHeadIndent 0, LineHeight 0/0, LineHeightMultiple 0, LineBreakMode 0, Tabs (\n    28L,\n    56L,\n    84L,\n    112L,\n    140L,\n    168L,\n    196L,\n    224L,\n    252L,\n    280L,\n    308L,\n    336L\n), DefaultTabInterval 0, Blocks (\n), Lists (\n), BaseWritingDirection -1, HyphenationFactor 0, TighteningForTruncation NO, HeaderLevel 0";
    }}
    
    )
    }
    
    opened by cdoky 8
  • Crash when emoji inside the text

    Crash when emoji inside the text

    Describe the bug If a string contains emoji then crash unexpectedly at Truncation.swift file line of code:

    let originalLine = lines[originalIndex]

    Please fix this issue as soon as possible.

    opened by samratpramanik 8
  • attributedLabel(_ label: NantesLabel, didSelectLink link: URL) doesn't seem to be working

    attributedLabel(_ label: NantesLabel, didSelectLink link: URL) doesn't seem to be working

    First off, thanks so much for making an awesome looking label replacement. In our app I'm currently doing a lot of work to manually calculate sizes in a UITextview for fields rendering markdown. I think this might be a great replacement if I can work out some of the kinks.

    I'm currently using this to try to make something like this:

    screen shot 2019-02-27 at 4 04 26 pm

    Anyway, thanks in advance for making it, and hopefully it's just user error on my part.

    Describe the bug Cannot get a link to trigger the delegate didSelectLink callback.

    To Reproduce Create a view controller and add code like this:

    class ViewController: UIViewController {
    
        var termsAndConditionsLabel: NantesLabel!
    
    
        // MARK: - Life Cycle
        override func viewDidLoad() {
            super.viewDidLoad()
            setupTermsLabel()
        }
    
        // MARK: - Setup
        private func setupTermsLabel() {
            termsAndConditionsLabel = NantesLabel(frame: .zero)
            termsAndConditionsLabel.numberOfLines = 0
            view.insertSubview(termsAndConditionsLabel, aboveSubview: stackView)
            termsAndConditionsLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(100); $0.left.equalToSuperview(); $0.right.equalToSuperview() }
            let termsText = "I understand by tapping \"Sign up\" my information will be used as described here and in the Privacy Policy, and I agree to the Terms."
            termsAndConditionsLabel.delegate = self
            termsAndConditionsLabel.text = termsText
            let url = URL(string: "https://www.google.com")!
            let privacyPolicyText = "Privacy Policy"
            let privacyPolicyRange = (termsText as NSString).range(of: privacyPolicyText)
            termsAndConditionsLabel.addLink(to: url, withRange: privacyPolicyRange)
        }
    }
    
    extension ViewController: NantesLabelDelegate {
        func attributedLabel(_ label: NantesLabel, didSelectLink link: URL) {
            print(link)
        }
    }
    

    Expected behavior I expected to see the link that was tapped printed to the console.

    Smartphone (please complete the following information):

    • Device: iOS Simulator on Xcode 10.1 (iPhoneX)
    • OS: 12.1
    opened by spadafiva 8
  • Add property to know if the text is more than `numberOfLines` long

    Add property to know if the text is more than `numberOfLines` long

    Is your feature request related to a problem? Please describe. In my code I want to have a button for Read more that pops a full text description into a new page. It would be nice to know if the label is currently displaying all the text so that I can determine whether or not to hide the Read more button

    Describe the solution you'd like I think it should be possible to have a variable on the label class that gets updated in the drawAttributedString(... function that tells if the text is fully displayed. I would be happy to create a class with a PR to add this functionality if this solution makes sense. I'm open to any bikeshedding about the property name. I think isTruncated, isShowingFullText, hasHiddenText could all be possibilities. Specifically, I think that we could put a flag before the for loop and then an update somewhere in here to update the status of that flag and update the label property after looping.

    //  ==> var showsFullText = true
    for lineIndex in 0..<lineOrigins.count {
       // ...
        if lineIndex == numberOfLines - 1 &&
            truncateLastLine &&
            !(lastLineRange.length == 0 && lastLineRange.location == 0) &&
            lastLineRange.location + lastLineRange.length < textRange.location + textRange.length {
            let truncationDrawingContext = TruncationDrawingContext(attributedString: attributedString, context: context, descent: descent, lastLineRange: lastLineRange, lineOrigin: lineOrigin, numberOfLines: numberOfLines, rect: rect)
    //  ==> showsFullText = false
            drawTruncation(truncationDrawingContext)
        } else { // otherwise normal drawing here
            let penOffset = CGFloat(CTLineGetPenOffsetForFlush(line, flushFactor, Double(rect.size.width)))
            let yOffset = lineOrigin.y - descent - font.descender
            context.textPosition = CGPoint(x: penOffset, y: yOffset)
            CTLineDraw(line, context)
        }
    }
    //  ==> self.showsFullText = showsFullText
    

    Describe alternatives you've considered It is possible to use a helper function to get the height of a view with a font size / attributed string, but it seems like there is a performance overhead and extra surface area that could potentially be removed.

    opened by spadafiva 7
  • Version 0.0.9 not available for cocoapods

    Version 0.0.9 not available for cocoapods

    Hi,

    Please support the newer release 0.0.9 for cocoapods. Please find the below logs:

    [!] CocoaPods could not find compatible versions for pod "Nantes": In Podfile: Nantes (= 0.0.9)

    None of your spec sources contain a spec satisfying the dependency: Nantes (= 0.0.9).

    You have either:

    • out-of-date source repos which you can update with pod repo update or with pod install --repo-update.
    • mistyped the name or version.
    • not added the source repo that hosts the Podspec to your Podfile.
    opened by sandeepgs1984 5
  • Calculate boundingRect get wrong height

    Calculate boundingRect get wrong height

    1. set attributedText to NantesLabel, include font, linespace;
    2. Get height by func boundingRect, could not get right height
    3. When I replace NantesLabel to UILabel, can get the right height
    opened by CivelXu 5
  • Setting an attributedText value containing link as String

    Setting an attributedText value containing link as String

    Hello, you did a fix related to this #15 issue and it is working fine only when attributed string contains URL as link. And as confirmation you can see this code:

    extension NSAttributedString {
        func findExistingLinks() -> Set<NSTextCheckingResult> {
            var relinks: Set<NSTextCheckingResult> = []
            enumerateAttribute(.link,
                               in: NSRange(location: 0, length: length),
                               options: []) { attribute, linkRange, _ in
                                if let url = attribute as? URL {
                                    relinks.insert(NSTextCheckingResult.linkCheckingResult(range: linkRange, url: url))
                                }
            }
            return relinks
        }
    }
    

    Why you didn't allow String ? So it would be more easier if handle this scenario also:

    if let linkString = attribute as? String, let url = URL(string: linkString) {
    

    The reason - from some parsing libraries it is coming as String not as URL

    opened by AlexZd 4
  • Click hyperlinks from HTML

    Click hyperlinks from HTML

    Is your feature request related to a problem? Please describe. I'd love it if hyperlinks from HTML could be clicked. At the moment I construct an NSMutableAttributedString from an HTML string (built-in functionality to iOS), which colors my links but I can not click them.

    Describe the solution you'd like An option to initialize NantesLabel with an HTML string so that it can parse the HTML and display clickable hyperlinks

    Describe alternatives you've considered Trying to calculate the range of links but I thought that'd be too difficult.

    Example String: "<a href="http://www.google.com">I can't be clicked"

    opened by WillBishop 4
  • Setting an attributedText value containing link attributes does not work

    Setting an attributedText value containing link attributes does not work

    Setting an attributedText value to the label that contains a .link: URL attribute does not work. This is because the checkText() method only looks at the raw string value of the attributedString for links. This means that links set that way do not work, they are not highlighted and are not clickable.

    Expected behavior Not sure if I should expect NantesLabel to handle this case but it would be good if it did.

    opened by ValCanBuild 4
  • When setting attributedText, existing attributes are overwritten (introduced in 0.0.5)

    When setting attributedText, existing attributes are overwritten (introduced in 0.0.5)

    When setting the attributedText property, the attributes that can be provided by Nantes will replace the ones in the NSAttributedString. This was introduced in 4b540bc.

    In my opinion it kind of defeats the purpose of having attributedText open.

    A few possible solutions:

    • Revert the change introduced in 0.0.5
    • Make attributedText unavailable from outside
    • Adjust the properties stored in NantesLabel when an attributed string is set (could be problematic as attributes can differ within the same text)
    opened by Marcocanc 3
  • Index out of range when using background fill or stroke

    Index out of range when using background fill or stroke

    When I attempt to set a background fill or stroke, like so:

    string.addAttribute(.nantesLabelBackgroundFillColor, value: UIColor.lightGray, range: range)

    App crashes with:

    Swift/ContiguousArrayBuffer.swift:600: Fatal error: Index out of range

    The termination point is Drawing.swift:198, when accessing origins[lineIndex].x: runBounds.origin.x = origins[lineIndex].x + rect.origin.x + xOffset - fillPadding.left - rect.origin.x

    I have verified that the NSRange I provide in the addAttribute call is valid, as I can use the nantesLabelStrikeOut attribute and it works fine.

    opened by clo-dan 0
  • Label UI doesn't update after setting text property

    Label UI doesn't update after setting text property

    Hello 👋, First of all, thank you for your library. I wanted to ask why the label textColor/font doesn't change after setting text property. for example:

    // this will work ✅               // this will not work ❌
    
    label.textColor = .red                label.text = "test"
    label.text  = "test"                  label.textColor = .red
    

    Is there a function to update the label UI ?

    opened by Bathant 0
  • touchesEnded and thus handleLinkTapped are never executed

    touchesEnded and thus handleLinkTapped are never executed

    I'm not sure why, but in my project the didSelectLink delegate method is never called. It seems that the root cause is that touchesEnded is also never called inside of NantesLabel, only touchesBegan and touchesCancelled.

    In my case the label is used within a UIStackView within a UITableViewCell, auto-sizing everything. I set myLabel.attributedText to an instance of NSAttributedString with the .foregroundColor and .font attributes set, and links are added the old-fashioned way via NSMutableAttributedString's addAttribute. The links are styled just fine (via linkAttributes), so they are definitely recognized. Also within touchesBegan I can see that it does find the link and sets activeLink. But.. touchesEnded is never called, so handleLinkTapped is also never executed.

    I'm not sure how easy it's going to be to make a minimum reproducible project 😰 But I'll see what I can do!

    opened by kevinrenskers 2
  • Is there any method supports long press at link?

    Is there any method supports long press at link?

    As the Title said. I have a feature that when I long press the link, I can show an action sheet. I think it's a common feature, so is there any method supports long press gesture? Like the property called 'linkLongPressBlock' in TTTAttributedLabelLink

    opened by jayden320 0
Releases(0.1.2)
  • 0.1.2(Nov 10, 2020)

  • 0.1.1(Jun 22, 2020)

    Platform:

    • Add compatibility with SwiftPM (#54)
      • Nantes can now be imported using Swift Package Manager.
    • [tools] use Mint to manage SwiftLint (#52)

    Enhancements:

    • Handle string as URL (#50)
    Source code(tar.gz)
    Source code(zip)
  • 0.0.9(Oct 3, 2019)

  • 0.0.8(Jun 13, 2019)

    Features & Fixes

    • Handle multi-line truncation (#24)
    • Update to swift 5 (#28)
    • Fix painting issue around scaled attributed strings (#27)
    • Added method to check wether the specified point contains a link (#32)
    Source code(tar.gz)
    Source code(zip)
  • 0.0.7(Apr 2, 2019)

    Bug Fixes:

    • Stop touches from being passed to superview if we handle taps with labelTappedBlock
    • Setting attributedText on a label with an attributed string that has link attributes inside it no longer removes the links inside the attributed string
    Source code(tar.gz)
    Source code(zip)
Owner
Instacart
Same-day Grocery Delivery
Instacart
Convert text with HTML tags, links, hashtags, mentions into NSAttributedString. Make them clickable with UILabel drop-in replacement.

Atributika is an easy and painless way to build NSAttributedString. It is able to detect HTML-like tags, links, phone numbers, hashtags, any regex or

Pavel Sharanda 1.1k Dec 26, 2022
A drop-in replacement for UILabel that supports attributes, data detectors, links, and more

TTTAttributedLabel A drop-in replacement for UILabel that supports attributes, data detectors, links, and more TTTAttributedLabel is a drop-in replace

TTTAttributedLabel 8.8k Dec 30, 2022
UILabel replacement with fine-grain appear/disappear animation

ZCAnimatedLabel UILabel-like view with easy to extend appear/disappear animation Features Rich text support (with NSAttributedString) Group aniamtion

Chen 2.3k Dec 5, 2022
A morphing UILabel subclass written in Swift.

LTMorphingLabel A morphing UILabel subclass written in Swift. The .Scale effect mimicked Apple's QuickType animation of iOS 8 of WWDC 2014. New morphi

Lex Tang 7.9k Jan 4, 2023
Lightweight library to set an Image as text background. Written in swift.

Simple and light weight UIView that animate text with an image.

Lucas Ortis 552 Sep 9, 2022
A triangle shaped corner label view for iOS written in Swift.

A triangle shaped corner label view for iOS written in Swift. This view is a subclass of UIView. It can be created and customized from the Storyboard

Mukesh Thawani 167 Nov 30, 2022
NSData Detector with swift

NSDataDetector This playground demonstrates a weird difference in behaviour between Xcodes 12.5.1 and 13.1 regarding NSDataDetector. Try to run it usi

Vinícius Rodrigues de Uzêda 0 Dec 7, 2021
MTLLinkLabel is linkable UILabel. Written in Swift.

# MTLLinkLabel is linkable UILabel. Written in Swift. Requirements iOS 8.0+ Xcode 7.3+ Installation Carthage You can install Carthage with Homebrew. $

Recruit Holdings. Media Technology Lab 74 Jul 1, 2022
Swift UIView for sliding text with page indicator

SlidingText for Swift Requirements Requires iOS 8 or later and Xcode 6.1+ Installation SlidingText is available through CocoaPods. To install it, simp

Dionysis Karatzas 53 Mar 1, 2022
A replacement for as which runs in constant time instead of O(n) when the conformance is not satisfiedA replacement for as which runs in constant time instead of O(n) when the conformance is not satisfied

ZConform A replacement for as? which runs in constant time instead of O(n) when the conformance is not satisfied. How it works ZConform does a one-tim

Emerge Tools 20 Aug 4, 2022
Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement. Supports Mac OS and Linux Line edit

Andy Best 114 Dec 14, 2022
FileManager replacement for Local, iCloud and Remote (WebDAV/FTP/Dropbox/OneDrive) files -- Swift

This Swift library provide a swifty way to deal with local and remote files and directories in a unified way. This library provides implementaion of W

Amir Abbas Mousavian 890 Jan 6, 2023
Replacement for Apple's Reachability re-written in Swift with closures

Reachability.swift Reachability.swift is a replacement for Apple's Reachability sample, re-written in Swift with closures. It is compatible with iOS (

Ashley Mills 7.7k Jan 1, 2023
A SlackTextViewController replacement written in Swift for the iPhone X.

Installation Just add MessageViewController to your Podfile and install. Done! pod 'MessageViewController' Setup You must subclass MessageViewControll

GitHawk 1.7k Jan 4, 2023
Custom UISegmentedControl replacement for iOS, written in Swift

TwicketSegmentedControl Custom UISegmentedControl replacement for iOS, written in Swift, used in the Twicket app. It handles the inertia of the moveme

Pol Quintana 1.7k Dec 31, 2022
A pixel perfect replacement for UITableView section index, written in Swift

MYTableViewIndex MYTableViewIndex is a re-implementation of UITableView section index. This control is usually seen in apps displaying contacts, track

Yury 520 Oct 27, 2022
A community-driven replacement for JSQMessagesViewController

A community-driven replacement for JSQMessagesViewController https://messagekit.github.io Goals Provide a ?? safe ?? environment for others to learn a

MessageKit 5.4k Jan 7, 2023
Realm is a mobile database: a replacement for Core Data & SQLite

Realm is a mobile database that runs directly inside phones, tablets or wearables. This repository holds the source code for the iOS, macOS, tvOS & wa

Realm 15.7k Jan 7, 2023
DeviceKit is a value-type replacement of UIDevice.

DeviceKit is a value-type replacement of UIDevice.

DeviceKit 3.9k Jan 6, 2023
Step-by-step progress view with labels and shapes. A good replacement for UIActivityIndicatorView and UIProgressView.

StepProgressView Step-by-step progress view with labels and shapes. A good replacement for UIActivityIndicatorView and UIProgressView. Usage let progr

Yonat Sharon 340 Dec 16, 2022