Displaying a PDFDocument in a PDFView allows the user to select parts of the document and perform actions e.g. "copy" with the selection.
How can selection be disabled in a PDFView while preserving the possibility for the user to zoom in and out and scroll in the PDF?
PDFView itself does not seem to offer such a property nor does the PDFViewDelegate.
You have to subclass PDFView, as such:
class MyPDFView: PDFView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
}
Just need to do is it will auto clear the selection and User will no longer long-press on PDF text.
class MyPDFView: PDFView {
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
self.currentSelection = nil
self.clearSelection()
return false
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
}
This below 2 lines need to add in canPerformAction()
self.currentSelection = nil
self.clearSelection()
For iOS 13, the above solution no longer works. It looks like they've changed the internal implementation of PDFView and specifically how the gesture recognizers are set up. I think generally it's discouraged to do this kind of thing, but it can still be done without using any internal API, here's how:
1) Recursively gather all subviews of PDFView (see below for the helper function to do this)
let allSubviews = pdfView.allSubViewsOf(type: UIView.self)
2) Iterate over them and deactivate any UILongPressGestureRecognizers:
for gestureRec in allSubviews.compactMap({ $0.gestureRecognizers }).flatMap({ $0 }) {
if gestureRec is UILongPressGestureRecognizer {
gestureRec.isEnabled = false
}
}
Helper func to recursively get all subviews of a given type:
func allSubViewsOf<T: UIView>(type: T.Type) -> [T] {
var all: [T] = []
func getSubview(view: UIView) {
if let aView = view as? T {
all.append(aView)
}
guard view.subviews.count > 0 else { return }
view.subviews.forEach{ getSubview(view: $0) }
}
getSubview(view: self)
return all
}
I'm calling the above code from the viewDidLoad method of the containing view controller.
I haven't yet found a good way to work this into a subclass of PDFView, which would be the preferred way for reusability and could just be an addition to the above NonSelectablePDFView. What I've tried so far is overriding didAddSubview and adding the above code after the call to super, but that didn't work as expected. It seems like the gesture recognizers are only being added at a later step, so figuring out when that is and if there's a way for the subclass to call some custom code after this happened would be a next step here.
With Swift 5 and iOS 12.3, you can solve your problem by overriding addGestureRecognizer(_:) method and canPerformAction(_:withSender:) method in a PDFView subclass.
import UIKit
import PDFKit
class NonSelectablePDFView: PDFView {
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
(gestureRecognizer as? UILongPressGestureRecognizer)?.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
As an alternative to the previous implementation, you can simply toggle UILongPressGestureRecognizer isEnabled property to false in the initializer.
import UIKit
import PDFKit
class NonSelectablePDFView: PDFView {
override init(frame: CGRect) {
super.init(frame: frame)
if let gestureRecognizers = gestureRecognizers {
for gestureRecognizer in gestureRecognizers where gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return false
}
}
You should note that this is not sufficient to disable text selecting, as there is a UITapAndHalfRecognizer as well – obviously a private Apple class - that also creates selections.
It is attached to the PDFDocumentView, which is another private implementation detail of PDFView, and which you can not replace with your own class implementation.
Related
How can I send action (similar kind of tap event) from sub class of UIImageView to View Controller.
Here is reference answer, that I want to apply for UIImageView. (UIImageView does not have UIControl in its super class hierarchy)
Following is my code but not working as I don't know, how to implement, what I need.
class TappableImageView: UIImageView {
// Initializer methods.....
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
// how can I send tap/click event to all view controllers, where I've used this image from this point.
}
}
Is there any alternate/other solution that may work for me?
I will recommend you to use a closure to handle taps:
class TappableImageView: UIImageView {
var handleTap: (() -> Void)? = nil
//------------------------------------------
private func addTapGesture(){
let tapOnImage = UITapGestureRecognizer(target: self, action: #selector(TappableImageView.handleTapGesture(tapGesture:)))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tapOnImage)
}
//----------------------------------------------
#objc func handleTapGesture(tapGesture: UITapGestureRecognizer) {
handleTap?()
}
}
And then I your view controller you can use this i.e. in viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
yourTappableImageView.handleTap = {
print("an image view was tapped")
}
}
It assumes that your TappableImageView is stored in variable named yourTappableImageView.
In my main app view which contains both a UITextField and a UITableView, I have the "usual" code using a UITapGestureRecognizer to dismiss the virtual keyboard if a tap is detected outside of the keyboard while I'm editing the contents of the UITextField.
An added feature is that this is only enabled if the virtual keyboard is actually shown - I don't want "background taps" to cause editing to end if the virtual keyboard isn't visible, but nor do I want background taps to trigger their normal behaviour - they should be consumed if the virtual keyboard is currently showing.
override func viewDidLoad() {
...
tapper = UITapGestureRecognizer(target: self, action: #selector(viewTapped))
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
view.addGestureRecognizer(tapper!)
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
view.removeGestureRecognizer(tapper!)
}
#IBAction func viewTapped(_ sender: AnyObject) {
view.endEditing(false)
}
This mostly works, except that the UITableView has interactive header cells which each also have a UITapGestureRecognizer attached.
The net result is that if I click on a header cell the gesture recognizer on that cell gets fired, and not the one on the parent view, and the keyboard doesn't get dismissed. If I click on the data cells instead, everything works fine.
If it matters, my UITableView has its own UIViewController subclass and is contained in a nested UIView - the table is too complicated to have that code in my main view controller.
How can I prevent the sub-view's gesture recognizers from handling these taps when the parent view's recognizer is attached so that the parent view can handle them instead?
I've implemented what I consider a "temporary" solution by also observing the virtual keyboard notifications in the UITableView's controller, tracking the keyboard visibility state, and then implementing this UIGestureRecognizerDelegate method on the header cells' gesture recognizer:
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return !keyboardShowing
}
This duplicates a certain amount of functionality from the main view in the sub-view which really shouldn't need to know about the state of the keyboard. I'm still looking for a method that can be handled entirely from within the parent view.
EDIT - with thanks to #Tommy for the hint, I now have a better solution that removes any need to track the keyboard state in the sub-view.
My parent view no longer uses a UIGestureRecognizer, but instead uses a custom subclass of UIView to track touch events, and conditionally ignore them:
class KeyboardDismissingView: UIView {
private var keyboardVisible = false
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let r = super.hitTest(point, with: event) else { return nil }
var v : UIView! = r
while v != nil {
if v is UITextField {
return r
}
v = v.superview
}
if keyboardVisible {
self.endEditing(false)
return nil
}
return r
}
func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardShown), name:
NSNotification.Name(rawValue: "UIKeyboardDidShowNotification"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardHidden), name:
NSNotification.Name(rawValue: "UIKeyboardDidHideNotification"), object: nil)
}
#IBAction func keyboardShown(_ sender: AnyObject) {
keyboardVisible = true
}
#IBAction func keyboardHidden(_ sender: AnyObject) {
keyboardVisible = false
}
override init (frame: CGRect) {
super.init(frame: frame)
setup()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
}
Does UILabel have any value that can be set in order to make it selectable?
I have a label that I want to be selectable, (long press and a copy btn shows up) kinda like in Safari.
Self-contained Solution (Swift 5)
You can adapt the solution from #BJHSolutions and NSHipster to make the following self-contained SelectableLabel:
import UIKit
/// Label that allows selection with long-press gesture, e.g. for copy-paste.
class SelectableLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
isUserInteractionEnabled = true
addGestureRecognizer(
UILongPressGestureRecognizer(
target: self,
action: #selector(handleLongPress(_:))
)
)
}
override var canBecomeFirstResponder: Bool {
return true
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return action == #selector(copy(_:))
}
// MARK: - UIResponderStandardEditActions
override func copy(_ sender: Any?) {
UIPasteboard.general.string = text
}
// MARK: - Long-press Handler
#objc func handleLongPress(_ recognizer: UIGestureRecognizer) {
if recognizer.state == .began,
let recognizerView = recognizer.view,
let recognizerSuperview = recognizerView.superview {
recognizerView.becomeFirstResponder()
UIMenuController.shared.setTargetRect(recognizerView.frame, in: recognizerSuperview)
UIMenuController.shared.setMenuVisible(true, animated:true)
}
}
}
Yes, you need to implement a UIMenuController from a long press gesture applied to your UILabel. There is an excellent article about this on NSHipster, but the gist of the article is the following.
Create a subclass of UILabel and implement the following methods:
override func canBecomeFirstResponder() -> Bool {
return true
}
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
return (action == "copy:")
}
// MARK: - UIResponderStandardEditActions
override func copy(sender: AnyObject?) {
UIPasteboard.generalPasteboard().string = text
}
Then in your view controller, you can add a long press gesture to your label:
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPressGesture:")
label.addGestureRecognizer(gestureRecognizer)
and handle the long press with this method:
func handleLongPressGesture(recognizer: UIGestureRecognizer) {
if let recognizerView = recognizer.view,
recognizerSuperView = recognizerView.superview
{
let menuController = UIMenuController.sharedMenuController()
menuController.setTargetRect(recognizerView.frame, inView: recognizerSuperView)
menuController.setMenuVisible(true, animated:true)
recognizerView.becomeFirstResponder()
}}
NOTE: This code is taken directly from the NSHipster article, I am just including it here for SO compliance.
UILabel inherits from UIView so you can just add a long press gesture recognizer to the label. Note that you have to change isUserInteractionEnabled to true, because it defaults to false for labels.
import UIKit
class ViewController: UIViewController {
let label = UILabel()
override func viewDidLoad() {
view.addSubview(label)
label.text = "hello"
label.translatesAutoresizingMaskIntoConstraints = false
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressLabel(longPressGestureRecognizer:)))
label.addGestureRecognizer(longPressGestureRecognizer)
label.isUserInteractionEnabled = true
}
#objc private func longPressLabel (longPressGestureRecognizer: UILongPressGestureRecognizer) {
if longPressGestureRecognizer.state == .began {
print("long press began")
} else if longPressGestureRecognizer.state == .ended {
print("long press ended")
}
}
}
I've implemented a UILabel subclass that provides all of the functionality needed. Note that if you're using this with interface builder, you'll need to adjust the init methods.
/// A label that can be copied.
class CopyableLabel: UILabel
{
// MARK: - Initialisation
/// Creates a new label.
init()
{
super.init(frame: .zero)
let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
self.addGestureRecognizer(gestureRecognizer)
self.isUserInteractionEnabled = true
}
required init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
// MARK: - Responder chain
override var canBecomeFirstResponder: Bool
{
return true
}
// MARK: - Actions
/// Method called when a long press is triggered.
func handleLongPressGesture(_ gestuerRecognizer: UILongPressGestureRecognizer)
{
guard let superview = self.superview else { return }
let menuController = UIMenuController.shared
menuController.setTargetRect(self.frame, in: superview)
menuController.setMenuVisible(true, animated:true)
self.becomeFirstResponder()
}
override func copy(_ sender: Any?)
{
UIPasteboard.general.string = self.text
}
}
I'm facing problem when string in UILabel is displayed with delay in inputAccessoryView on UIViewController. I have attached gif demonstrating this problem. After pushing SecondViewController to navigation stack inputAccessoryView is missing text for short time. But I want text to be shown right away after opening screen.
Implementation demonstrating this problem is extremely simple.
class SecondViewController: UIViewController {
#IBOutlet var accessoryView: UIView!
override var inputAccessoryView: UIView {
return accessoryView
}
override func canBecomeFirstResponder() -> Bool {
return true
}
}
Does any one have solution for this problem?
I have come up with the solution which works on both iOS 8 and 9. Also it address retain cycle issue presented in iOS 9 which prevent view controller from being deallocated when use inputaccessoryview. Check github project for more details.
With lots of experimentation I have found quite hacky solution but works like a charm. Just subclass your implemantation accessory view from AccessoryView listed below.
class AccessoryView: UITextField {
override var canBecomeFirstResponder: Bool {
return true
}
override func awakeFromNib() {
super.awakeFromNib()
disableShowingKeyboard()
hideCursor()
}
}
extension AccessoryView {
private func disableShowingKeyboard() {
inputView = UIView()
}
private func hideCursor() {
tintColor = UIColor.clear
}
override func accessibilityActivate() -> Bool {
return false
}
override var isEditing: Bool {
return false
}
override func caretRect(for position: UITextPosition) -> CGRect {
return .zero
}
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
return []
}
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(UIResponder.copy(_:)) || action == #selector(UIResponder.selectAll(_:)) || action == #selector(UIResponder.paste(_:)){
return false
}
return super.canPerformAction(action, withSender: sender)
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer is UILongPressGestureRecognizer {
gestureRecognizer.isEnabled = false
}
super.addGestureRecognizer(gestureRecognizer)
}
}
extension AccessoryView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for view in subviews {
let _point = self.convert(point, to: view)
if !view.isHidden && view.isUserInteractionEnabled && view.alpha > 0.01 && view.point(inside: _point, with: event) {
if let _view = view.hitTest(_point, with: event){
return _view
}
}
}
return super.hitTest(point, with: event)
}
}
I'm trying to get a setup similar to what Facebook use (if they use a UITextView). I want links to be detected automatically however I don't want any other text in the UITextView selectable. So, the user can click on the link but is unable to select any other text.
Despite searching around, I've yet to come across a solution as for link selection to work it requires the whole of the text view to be selectable.
if your minimum deployment target is iOS 11.2 or newer
You can disable text selection by subclassing UITextView and forbidding the gestures that can select something.
The below solution is:
compatible with isEditable
compatible with isScrollEnabled
compatible with links
/// Class to allow links but no selection.
/// Basically, it disables unwanted UIGestureRecognizer from UITextView.
/// https://stackoverflow.com/a/49428307/1033581
class UnselectableTappableTextView: UITextView {
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer is UIPanGestureRecognizer {
// required for compatibility with isScrollEnabled
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
tapGestureRecognizer.numberOfTapsRequired == 1 {
// required for compatibility with links
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// allowing smallDelayRecognizer for links
// https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable
if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
// comparison value is used to distinguish between 0.12 (smallDelayRecognizer) and 0.5 (textSelectionForce and textLoupe)
longPressGestureRecognizer.minimumPressDuration < 0.325 {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// preventing selection from loupe/magnifier (_UITextSelectionForceGesture), multi tap, tap and a half, etc.
gestureRecognizer.isEnabled = false
return false
}
}
if your minimum deployment target is iOS 11.1 or older
Native UITextView links gesture recognizers are broken on iOS 11.0-11.1 and require a small delay long press instead of a tap: Xcode 9 UITextView links no longer clickable
You can properly support links with your own gesture recognizer and you can disable text selection by subclassing UITextView and forbidding the gestures that can select something or tap something.
The below solution will disallow selection and is:
compatible with isScrollEnabled
compatible with links
workaround limitations of iOS 11.0 and iOS 11.1, but loses the UI effect when tapping on text attachments
/// Class to support links and to disallow selection.
/// It disables most UIGestureRecognizer from UITextView and adds a UITapGestureRecognizer.
/// https://stackoverflow.com/a/49428307/1033581
class UnselectableTappableTextView: UITextView {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// Native UITextView links gesture recognizers are broken on iOS 11.0-11.1:
// https://stackoverflow.com/questions/46143868/xcode-9-uitextview-links-no-longer-clickable
// So we add our own UITapGestureRecognizer.
linkGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(textTapped))
linkGestureRecognizer.numberOfTapsRequired = 1
addGestureRecognizer(linkGestureRecognizer)
linkGestureRecognizer.isEnabled = true
}
var linkGestureRecognizer: UITapGestureRecognizer!
// required to prevent blue background selection from any situation
override var selectedTextRange: UITextRange? {
get { return nil }
set {}
}
override func addGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
// Prevents drag and drop gestures,
// but also prevents a crash with links on iOS 11.0 and 11.1.
// https://stackoverflow.com/a/49535011/1033581
gestureRecognizer.isEnabled = false
super.addGestureRecognizer(gestureRecognizer)
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == linkGestureRecognizer {
// Supporting links correctly.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
if gestureRecognizer is UIPanGestureRecognizer {
// Compatibility support with isScrollEnabled.
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
// Preventing selection gestures and disabling broken links support.
gestureRecognizer.isEnabled = false
return false
}
#objc func textTapped(recognizer: UITapGestureRecognizer) {
guard recognizer == linkGestureRecognizer else {
return
}
var location = recognizer.location(in: self)
location.x -= textContainerInset.left
location.y -= textContainerInset.top
let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
let characterRange = NSRange(location: characterIndex, length: 1)
if let attachment = attributedText?.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: attachment, in: characterRange)
}
}
if let url = attributedText?.attribute(.link, at: characterIndex, effectiveRange: nil) as? URL {
if #available(iOS 10.0, *) {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange, interaction: .invokeDefaultAction)
} else {
_ = delegate?.textView?(self, shouldInteractWith: url, in: characterRange)
}
}
}
}
You can subclass the UITextView overriding the method of selectedTextRange, setting it to nil. And the links will still be clickable, but you won't be able to select the rest of the text (even the link but you can click on it).
class CustomTextView: UITextView {
override public var selectedTextRange: UITextRange? {
get {
return nil
}
set { }
}
You need to subclass UITextView and override gestureRecognizerShouldBegin (_:) method like this:
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if isEditable == false {
if let gesture = gestureRecognizer as? UILongPressGestureRecognizer, gesture.minimumPressDuration == 0.5 {
return false
}
}
return true
}
this will prevent from textview being selected but link will work as expected
Edited:
It turned out that when double tap and hold you are still able to select text. As I figured out it happens after two taps(not the UITapGesture with property "minimalNumberOfTaps", but to different taps one after another), so the solution is to track time after first step (approx. 0.7 sec)
Full code:
var lastTapTime: TimeInterval = 0
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if isEditable == false {
if let gesture = gestureRecognizer as? UILongPressGestureRecognizer, gesture.minimumPressDuration == 0.5 {
return false
}
}
if Date().timeIntervalSince1970 >= lastTapTime + 0.7 {
lastTapTime = Date().timeIntervalSince1970
return true
} else {
return false
}
}
This is not the most elegant solution but it seems to work 🤷♂️
The selected answer doesn't work in my case, and I'm not comfortable with comparing unconfirmed values inside of internal UIGestureRecognizers.
My solution was to override point(inside:with:) and allow a tap-through when the user is not touching down on linked text: https://stackoverflow.com/a/44878203/1153630
This is what worked for me;
class LinkDetectingTextView: UITextView {
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if isEditable == false {
if let _ = gestureRecognizer as? UITapGestureRecognizer {
return false
}
if let longPressRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
longPressRecognizer.minimumPressDuration == 0.5 { // prevent to select text but allow certain functionality in application
return false
}
}
return true
}
}
In addition, set minimumPressDuration of the longPressGestureRecognizer in the application another value different than 0.5.
This answer is for iOS 10.3.x and below where your UIView is not embedded in a subview. For a more robust, modern answer, please see Cœur's answer below.
You need to prevent the UITextView from becoming first responder.
1. Subclass UITextView to your own custom class (MyTextView).
2. Override canBecomeFirstResponder(). Here's an example in Swift:
Swift 3:
class MyTextView: UITextView {
override func becomeFirstResponder() -> Bool {
return false
}
}
Swift 2:
class MyTextView: UITextView {
override func canBecomeFirstResponder() -> Bool {
return false
}
}
Any links detected will still be enabled. I tested this with a phone number.