- May 27, 2025
- Mins Read
![]() |
![]() |
![]() |
UILabel
and UITextView
offer unsatisfying support for text selection.
Existing solutions like TTTAttributedLabel are great but offer a somewhat limited API for text selection.
Add the following to your Podfile
pod ‘SelectableTextView’, ‘~> 1.0.2’
Add the following to your Cartfile
github “jhurray/SelectableTextView” ~> 1.0.2
Clone the repo and manually add the Files in /SelectableTextView
import SelectableTextView
let textView = SelectableTextView()
textView.text = “Hello World!”
textView.truncationMode = .truncateTail
textView.alignment = .center
textView.numberOfLines = 1
let greetingValidator = MatchesTextValidator(text: “hello”)
textView.registerValidator(_ validator: greetingValidator) { (validText, validator) in
// Handle selection of “Hello”
}
let exclamationValidator = SuffixValidator(suffix: “!”)
textView.registerValidator(_ validator: exclamationValidator) { (validText, validator) in
// Handle selection of “World!”
}
To create selectable text, you have to create and register a validator. The validator must conform to the TextSelectionValidator
protocol.
let hashtagValidator = PrefixValidator(prefix: “#”)
textView.registerValidator(validator: hashtagValidator) { (validText, validator) in
// Handle selection of hashtag
}
You can unregister a validator at any time.
textView.removeValidator(validator: hashtagValidator)
Here is a resource for creating custom validators using the TextSelectionValidator
protocol.
There are other more specific protocols that make customization easier like ContainerTextSelectionValidator
and CompositeTextSelectionValidator
.
There 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.
MatchesTextValidator(text: String, caseSensitive: Bool = false)
ContainsTextValidator(text: String, caseSensitive: Bool = false)
PrefixValidator(text: String, caseSensitive: Bool = false)
SuffixValidator(text: String, caseSensitive: Bool = false)
HashtagTextValidator()
AtSymbolTagTextValidator()
QuotationsTextValidator()
HandlebarsValidator(searchableText: String, replacementText: String)
ReverseValidator(validator: TextSelectionValidator)
ContainerValidator(validator: TextSelectionValidator, selectionAttributes: [String: Any]? = nil)
CompositeValidator(validators: [TextSelectionValidator], selectionAttributes: [String: Any]? = nil)
LinkValidator() // Validates any link (HTTP, HTTPS, file, etc…)
HTTPLinkValidator() // Validates HTTP and HTTPS links
UnsafeLinkValidator() // Validates HTTP links
HTTPSLinkValidator()
CustomLinkValidator(urlString: String!, replacementText: String? = nil)
Customization is possible using the LinkValidatorAttributes
protocol. Example here.
RegexValidator(pattern: String, options: NSRegularExpression.Options = .caseInsensitive)
EmailValidator()
PhoneNumberValidator()
Text Expansion
You can add a text expansion button with the following method:
public func addExpansionButton(collapsedState: (text: String, lines: Int), expandedState: (text: String, lines: Int), attributes: [String: Any]? = nil)
You can remove the expansion button using the following method:
public func removeExpansionButton(numberOfLines: Int = 1)
Example:
let attributes = [NSForegroundColorAttributeName: purple]
textView.addExpansionButton(collapsedState: (“More…”, 2),
expandedState: (“Less”, 0),
attributes: attributes)
…
textView.removeExpansionButton(numberOfLines: 2)
You can customize the background color of the expansion button using the SelectedBackgroundColorAttribute
property HighlightedTextSelectionAttributes
struct as an attribute key.
let attributes: [String: Any] = [HighlightedTextSelectionAttributes.SelectedBackgroundColorAttribute : UIColor.purple]
String?
UIFont
UIFont.systemFont(ofSize: 17)
UIColor
UIColor.darkText
text
and textColor
with the attributed textNSAttributedString?
nil
TextAlignment
.left
, .right
, .center
.left
LineBreakMode
.wordWrap
. wordWrap
TruncationMode
.clipping
, .truncateTail
.clipping
Int
0
UILabel
CGFloat
0
UIEdgeInsets
UIEdgeInsets.zero
[String : AnyObject]?
color
= tintColor
, font
= boldSystemFont(ofSize: font.pointSize + 2)
Bool?
nil
. Will only return a value if the expansion button is addedCGSize
Bool
true
Bool
false
SelectableTextViewDelegate?
SelectableTextViewScrollDelegate?
\n
\t
\0
If you want to have text next to to a selectabe portion of text but still validate the text correctly, use the null terminator.
let text = “The period next to the #Hashtag\0. Will not be highlighted if I use a hashtag validator.”
You 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.
public func framesOfWordsMatchingValidator(_ validator: TextSelectionValidator) -> [CGRect]
You can adjust the number of spaces a tab character creates using TabTextModelConfig.numberOfSpaces
. The default value is 4.
TabTextModelConfig.numberOfSpaces = 2
You can set most customization properties via interface builder. SelectableTextView
is marked as @IBDesignable
.
numberOfLines: Int
text: String
textColor: UIColor
lineSpacing: Float
isSelectionEnabled: Bool
isScrollEnabled: Bool
fontSize: Float
truncateTail: Bool
topTextInsets: Float
bottomTextInsets: Float
leftTextInsets: Float
rightTextInsets: Float
Default implementations are provided for all SelectableTextViewDelegate
methods.
public protocol SelectableTextViewDelegate: class {
/// Resolves conflict between multiple validates that return `true` from their `validate:` method
//
// i.e. PrefixTextValidator for `#` and `#my` will both return true for `#myCoolHashtag`,
// but the actions they are registered for may differ
//
/// Default behavior is to choose the first validator in the composite validator’s `validators` array
func resolveValidationConflictsForSelectableTextView(textView: SelectableTextView, conflictingValidators: [TextSelectionValidator]) -> TextSelectionValidator
/// Defaults to `false`
func animateExpansionButtonForSelectableTextView(textView: SelectableTextView) -> Bool
/// Defaults to `.truncateTail`
func truncationModeForWordsThatDontFitForSelectableTextView(textView: SelectableTextView) -> TruncationMode
/// Optional, Default empty implementation provideed
func selectableTextViewContentHeightDidChange(textView: SelectableTextView, oldHeight: CGFloat, newHeight: CGFloat)
}
SelectableTextView
supports scrolling and forwards scroll events through SelectableTextViewScrollDelegate
.
public protocol SelectableTextViewScrollDelegate: class {
func selectableTextViewDidScroll(_ scrollView: UIScrollView)
func selectableTextViewWillBeginDragging(_ scrollView: UIScrollView)
func selectableTextViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
func selectableTextViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool)
func selectableTextViewWillBeginDecelerating(_ scrollView: UIScrollView)
func selectableTextViewDidEndDecelerating(_ scrollView: UIScrollView)
func selectableTextViewDidEndScrollingAnimation(_ scrollView: UIScrollView)
}
You can also scroll to specific words or the first word that passes a validator.
/// Scrolls to the first instance of the word
/// Attempts to match the text and display text of a word
public func scrollToWord(_ word: String, position: ScrollPosition, animated: Bool)
/// Scrolls to the first instance of a word that passes the provided TextSelectionValidator
public func scrollToWordPassingValidator(_ validator: TextSelectionValidator, position: ScrollPosition, animated: Bool)
Light and scrollable view controller for tvOS to present blocks of text Description TvOSTextViewer is a view controller to present ...
TvOSSlider is an implementation of UISlider for tvOS. Description and usage TvOSSlider palliates missing an implementation of UISlider for tvOS as part ...
TvOSScribble, based on CoreML, mitigates the lack of a physical numpad area in Siri Remote implementing a handwriting gesture recognizer. ...
PIN keyboard for tvOS Description TvOSPinKeyboard is a view controller that allows easily asking for PIN codes in tvOs Requirements ...
📺 A tvOS button which truncates long text with '... More'. The TvOSMoreButton is a simple view which aims to ...