{"id":2959,"url":"https://github.com/jhurray/SelectableTextView","last_synced_at":"2025-08-03T12:31:55.695Z","repository":{"id":62455161,"uuid":"83172115","full_name":"jhurray/SelectableTextView","owner":"jhurray","description":"A text view that supports selection and expansion","archived":false,"fork":false,"pushed_at":"2020-07-09T15:00:14.000Z","size":902,"stargazers_count":633,"open_issues_count":4,"forks_count":28,"subscribers_count":15,"default_branch":"master","last_synced_at":"2024-11-20T09:44:54.124Z","etag":null,"topics":["expansion-button","interface-builder","ios-sdk","ios-ui","swift4","text-expansion","textview","validators"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jhurray.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-26T00:15:49.000Z","updated_at":"2024-10-17T01:30:05.000Z","dependencies_parsed_at":"2022-11-02T00:00:37.202Z","dependency_job_id":null,"html_url":"https://github.com/jhurray/SelectableTextView","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FSelectableTextView","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FSelectableTextView/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FSelectableTextView/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhurray%2FSelectableTextView/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhurray","download_url":"https://codeload.github.com/jhurray/SelectableTextView/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228438869,"owners_count":17920014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["expansion-button","interface-builder","ios-sdk","ios-ui","swift4","text-expansion","textview","validators"],"created_at":"2024-01-05T20:16:27.440Z","updated_at":"2024-12-07T00:31:10.487Z","avatar_url":"https://github.com/jhurray.png","language":"Swift","funding_links":[],"categories":["UI","Swift","Content"],"sub_categories":["TextField \u0026 TextView","Other free courses","Text View"],"readme":"\u003cimg class=\"center\" src=\"./Resources/Logo.png\"\u003e\n\u003ctable\u003e\u003ctr\u003e\n\u003ctd width=33%\u003e\u003cimg src=\"./Resources/Feature1.png\"\u003e\u003c/td\u003e\n\u003ctd width=33%\u003e\u003cimg src=\"./Resources/Feature2.png\"\u003e\u003c/td\u003e\n\u003ctd width=33%\u003e\u003cimg src=\"./Resources/Feature3.png\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\u003ctr\u003e\n\u003ctd width=33%\u003e\u003cp align=\"center\"\u003e\u003cimg src=\"./Resources/SelectableTextViewDemo1.gif\"\u003e\u003c/p\u003e\u003c/td\u003e\n\u003ctd width=33%\u003e\u003cp align=\"center\"\u003e\u003cimg src=\"./Resources/SelectableTextViewDemo2.gif\"\u003e\u003c/p\u003e\u003c/td\u003e\n\u003ctd width=33%\u003e\u003cp align=\"center\"\u003e\u003cimg src=\"./Resources/SelectableTextViewDemo3.gif\"\u003e\u003c/p\u003e\u003c/td\u003e\n\u003c/tr\u003e\u003c/table\u003e\n\n## The Problem\n`UILabel` and `UITextView` offer unsatisfying support for text selection.\n\nExisting solutions like [TTTAttributedLabel](https://github.com/TTTAttributedLabel/TTTAttributedLabel) are great but offer a somewhat limited API for text selection.\n\n## Features\n* [Text Selection](#text-selection)\n* [Text Expansion](#text-expansion)\n* [Customization](#customization)\n* [Prewritten Selection Validators](#validators)\n* [Interface Builder](#interface-builder)\n* [Scrolling](#scrolling)\n\n## Installation\n\n#### CocoaPods\n\nAdd the following to your `Podfile`\n\n```ruby\npod 'SelectableTextView', '~\u003e 1.0.2'\n```\n\n#### Carthage\n\nAdd the following to your `Cartfile`\n\n```ruby\ngithub \"jhurray/SelectableTextView\" ~\u003e 1.0.2\n```\n\n#### Add to project Manually\nClone the repo and manually add the Files in [/SelectableTextView](./SelectableTextView)\n\n## Usage\n\n```swift\nimport SelectableTextView\n\nlet textView = SelectableTextView()\ntextView.text = \"Hello World!\"\ntextView.truncationMode = .truncateTail\ntextView.alignment = .center\ntextView.numberOfLines = 1\n\nlet greetingValidator = MatchesTextValidator(text: \"hello\")\ntextView.registerValidator(_ validator: greetingValidator) { (validText, validator) in\n\t// Handle selection of \"Hello\"\n}\n\nlet exclamationValidator = SuffixValidator(suffix: \"!\")\ntextView.registerValidator(_ validator: exclamationValidator) { (validText, validator) in\n\t// Handle selection of \"World!\"\n}\n\n```\n\n## Text Selection\u003ca name=\"text-selection\"\u003e\u003c/a\u003e\n\nTo create selectable text, you have to create and register a validator. The validator must conform to the `TextSelectionValidator` protocol.\n\n```swift\nlet hashtagValidator = PrefixValidator(prefix: \"#\")\ntextView.registerValidator(validator: hashtagValidator) { (validText, validator) in\n\t// Handle selection of hashtag\n}\n```\n\nYou can unregister a validator at any time.\n\n```swift\ntextView.removeValidator(validator: hashtagValidator)\n```\n\n### Custom Validators\n\nHere is a resource for [creating custom validators](https://github.com/jhurray/SelectableTextView/wiki/Custom-Validators) using the `TextSelectionValidator` protocol. \n\nThere are other more specific protocols that make customization easier like `ContainerTextSelectionValidator` and `CompositeTextSelectionValidator`.\n\n### Prewritten Validators\u003ca name=\"validators\"\u003e\u003c/a\u003e\n\nThere are a few prewritten validators supplied. These can be used as they are, as building blocks for other more complex validators, and as examples on how to build custom validators.\n\n##### Text Validators\n```swift\nMatchesTextValidator(text: String, caseSensitive: Bool = false)\n\nContainsTextValidator(text: String, caseSensitive: Bool = false)\n\nPrefixValidator(text: String, caseSensitive: Bool = false)\n\nSuffixValidator(text: String, caseSensitive: Bool = false)\n\nHashtagTextValidator()\n\nAtSymbolTagTextValidator()\n\nQuotationsTextValidator()\n\nHandlebarsValidator(searchableText: String, replacementText: String)\n``` \n\n##### Abstract Validators\n```swift\nReverseValidator(validator: TextSelectionValidator)\n\nContainerValidator(validator: TextSelectionValidator, selectionAttributes: [String: Any]? = nil)\n\nCompositeValidator(validators: [TextSelectionValidator], selectionAttributes: [String: Any]? = nil)\n```\n\n##### Link Validators\n```swift\nLinkValidator() // Validates any link (HTTP, HTTPS, file, etc...)\n\nHTTPLinkValidator() // Validates HTTP and HTTPS links\n\nUnsafeLinkValidator() // Validates HTTP links\n\nHTTPSLinkValidator()\n\nCustomLinkValidator(urlString: String!, replacementText: String? = nil) \n```\n\nCustomization is possible using the `LinkValidatorAttributes` protocol. Example [here](https://github.com/jhurray/SelectableTextView/wiki/Link-Validators).\n\n##### Regex Validators\n```swift\nRegexValidator(pattern: String, options: NSRegularExpression.Options = .caseInsensitive)\n\nEmailValidator()\n\nPhoneNumberValidator()\n```\n\n## Text Expansion\u003ca name=\"text-expansion\"\u003e\u003c/a\u003e\n\n\u003cimg align=\"right\" src=\"./Resources/ExpansionDemo.gif\"\u003e\n\nYou can add a text expansion button with the following method:\n\n\u003cbr\u003e\n\n```swift\npublic func addExpansionButton(collapsedState: (text: String, lines: Int), expandedState: (text: String, lines: Int), attributes: [String: Any]? = nil)\n```\n\nYou can remove the expansion button using the following method:\n\n```swift\npublic func removeExpansionButton(numberOfLines: Int = 1)\n```\n\nExample:\n\n```swift\nlet attributes = [NSForegroundColorAttributeName: purple]\ntextView.addExpansionButton(collapsedState: (\"More...\", 2),\n                             expandedState: (\"Less\", 0),\n                                attributes: attributes)\n                                \n...\n\ntextView.removeExpansionButton(numberOfLines: 2)\n```\n\nYou can customize the background color of the expansion button using the `SelectedBackgroundColorAttribute` property `HighlightedTextSelectionAttributes` struct as an attribute key.\n\n```swift\nlet attributes: [String: Any] = [HighlightedTextSelectionAttributes.SelectedBackgroundColorAttribute : UIColor.purple]\n```\n\n## Customization\u003ca name=\"customization\"\u003e\u003c/a\u003e\n\n#### text\n* Sets the content of the text view\n* Type: `String?`\n\n#### font\n* Sets the font of the text view\n* Type: `UIFont`\n* Defaults to `UIFont.systemFont(ofSize: 17)`\n\n#### textColor\n* Sets the default text color\n* Type: `UIColor`\n* Defaults to `UIColor.darkText`\n\n#### attributedText\n* Overrides the `text` and `textColor` with the attributed text\n* Type: `NSAttributedString?`\n* Defaults to `nil`\n\n#### textAlignment\n* Alignment of text in the text view\n* Type: `TextAlignment`\n* Supports 3 types: `.left`, `.right`, `.center`\n* Defaults to `.left`\n\n#### lineBreakMode\n* Determines how the text view handles new lines\n* Type: `LineBreakMode`\n* Supports 1 type: `.wordWrap`\n* * Defaults to `. wordWrap `\n* See [Goals](#goals)\n\n#### truncationMode\n* Determines the bahavior of the last word in the last line of the text view\n* Type: `TruncationMode`\n* Supports 2 types: `.clipping`, `.truncateTail`\n* Defaults to `.clipping`\n* See [Goals](#goals)\n\n#### numberOfLines\n* Determines the number of lines in the text view\n* Type: `Int`\n* Defaults to `0`\n* 0 lines means unbounded, similar to `UILabel`\n\n#### lineSpacing\n* Determines the spacing between lines\n* Type: `CGFloat`\n* Defaults to `0`\n* Supports negative values\n\n#### textContainerInsets\n* Sets the content inset of the text view\n* Type: `UIEdgeInsets`\n* Defaults to `UIEdgeInsets.zero`\n\n#### selectionAttributes\n* Sets the default selection attributes for selectable text\n* Type: `[String : AnyObject]?`\n* Defaults: `color` = `tintColor`, `font` = `boldSystemFont(ofSize: font.pointSize + 2)`\n\n#### isExpanded\n* Tracks the state of the expansion button\n* Type: `Bool?`\n* Defaults to `nil`. Will only return a value if the expansion button is added\n* If the expansion button is added, this property will toggle the state\n\n#### textContentSize\n* Readonly, returns the size of the text content\n* Type: `CGSize`\n\n#### isSelectionEnabled\n* Determines if selection is enabled for the text view\n* Type: `Bool`\n* Defaults to `true`\n\n#### isScrollEnabled\n* Determines if scrolling is enabled for the text view\n* Type: `Bool`\n* Defaults to `false`\n\n#### scrollDelegate\n* Forwards scrolling events fron the text view\n* Type: `SelectableTextViewDelegate?`\n\n#### delegate\n* Delegates work for the text view\n* Type: `SelectableTextViewScrollDelegate?`\n\n## Supported Escape Characters\n* New Line `\\n`\n* Tab `\\t`\n* Null Terminator `\\0`\n\nIf you want to have text next to to a selectabe portion of text but still validate the text correctly, use the null terminator.\n\n```swift\nlet text = \"The period next to the #Hashtag\\0. Will not be highlighted if I use a hashtag validator.\"\n```\n\n## Miscelaneous\n\n##### framesOfWordsMatchingValidator\nYou can get the relative frames of words within the text view with the method below. This is how I set up the stars effect in the first example gif.\n\n```swift\npublic func framesOfWordsMatchingValidator(_ validator: TextSelectionValidator) -\u003e [CGRect]\n```\n\n##### Tab Length\n\nYou can adjust the number of spaces a tab character creates using `TabTextModelConfig.numberOfSpaces`. The default value is 4.\n\n```swift\nTabTextModelConfig.numberOfSpaces = 2\n```\n\n## Interface Builder\u003ca name=\"interface-builder\"\u003e\u003c/a\u003e\n\nYou can set most customization properties via interface builder. `SelectableTextView` is marked as `@IBDesignable`.\n\n\u003cimg align=\"right\" width=65% src=\"./Resources/IBFeature.png\"\u003e\u003c/img\u003e\n\n* `numberOfLines: Int`\n* `text: String`\n* `textColor: UIColor`\n* `lineSpacing: Float`\n* `isSelectionEnabled: Bool`\n* `isScrollEnabled: Bool`\n* `fontSize: Float`\n* `truncateTail: Bool`\n* `topTextInsets: Float`\n* `bottomTextInsets: Float`\n* `leftTextInsets: Float`\n* `rightTextInsets: Float`\n\n## Delegate\n\nDefault implementations are provided for all `SelectableTextViewDelegate` methods.\n\n```swift\npublic protocol SelectableTextViewDelegate: class {\n    \n    /// Resolves conflict between multiple validates that return `true` from their `validate:` method\n    //\n    // i.e. PrefixTextValidator for `#` and `#my` will both return true for `#myCoolHashtag`,\n    // but the actions they are registered for may differ\n    //\n    /// Default behavior is to choose the first validator in the composite validator's `validators` array\n    func resolveValidationConflictsForSelectableTextView(textView: SelectableTextView, conflictingValidators: [TextSelectionValidator]) -\u003e TextSelectionValidator\n    \n    /// Defaults to `false`\n    func animateExpansionButtonForSelectableTextView(textView: SelectableTextView) -\u003e Bool\n    \n    /// Defaults to `.truncateTail`\n    func truncationModeForWordsThatDontFitForSelectableTextView(textView: SelectableTextView) -\u003e TruncationMode\n    \n    /// Optional, Default empty implementation provideed\n    func selectableTextViewContentHeightDidChange(textView: SelectableTextView, oldHeight: CGFloat, newHeight: CGFloat)\n}\n```\n\n## Scrolling\u003ca name=\"scrolling\"\u003e\u003c/a\u003e\n\n`SelectableTextView` supports scrolling and forwards scroll events through `SelectableTextViewScrollDelegate`.\n\n```swift\npublic protocol SelectableTextViewScrollDelegate: class {\n    \n    func selectableTextViewDidScroll(_ scrollView: UIScrollView)\n    func selectableTextViewWillBeginDragging(_ scrollView: UIScrollView)\n    func selectableTextViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer\u003cCGPoint\u003e)\n    func selectableTextViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)\n    func selectableTextViewWillBeginDecelerating(_ scrollView: UIScrollView)\n    func selectableTextViewDidEndDecelerating(_ scrollView: UIScrollView)\n    func selectableTextViewDidEndScrollingAnimation(_ scrollView: UIScrollView)\n}\n```\n\nYou can also scroll to specific words or the first word that passes a validator.\n\n```swift\n/// Scrolls to the first instance of the word\n/// Attempts to match the text and display text of a word\npublic func scrollToWord(_ word: String, position: ScrollPosition, animated: Bool)\n    \n   /// Scrolls to the first instance of a word that passes the provided TextSelectionValidator\npublic func scrollToWordPassingValidator(_ validator: TextSelectionValidator, position: ScrollPosition, animated: Bool)\n```\n\n\n## Goals\u003ca name=\"goals\"\u003e\u003c/a\u003e\n\n* Character wrapping\n* More truncation styles: `.head`, `.center`\n\n## Contact Info \u0026\u0026 Contributing\n\nFeel free to email me at [jhurray33@gmail.com](mailto:jhurray33@gmail.com). I'd love to hear your thoughts on this, or see examples where this has been used.\n\n[MIT License](https://github.com/jhurray/SelectableTextView/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhurray%2FSelectableTextView","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhurray%2FSelectableTextView","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhurray%2FSelectableTextView/lists"}