Add UITapGestureRecognizer to UITextView without blocking textView touches
To do that make your view controller adopt to UIGestureRecognizerDelegate and override should recognize simultaneously with gesture recognizer method like:
override func viewDidLoad() {
tapTerm = UITapGestureRecognizer(target: self, action: "tapTextView:")
tapTerm.delegate = self
textView.addGestureRecognizer(tapTerm)
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
How To Add One UIGestureRecognizer to All UITextView without blocking textView touches
first u must add uitextfield
delegate
<UITextFieldDelegate>
then use that method
(BOOL)textFieldShouldBeginEditing:(UITextField *) textField
UITextView's UIGestureRecognizer preventing keyboard from appearing on tap
You could use the UIGestureRecognizerDelegate
to make the UITapGestureRecognizer
work along the regular UITextView
behaviour:
class TestViewController: UIViewController {
@IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap))
tapGestureRecognizer.delegate = self
textView.addGestureRecognizer(tapGestureRecognizer)
}
@objc private func tap() {
print("tap")
}
}
extension TestViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
UITextView with Gesture Recognizer - Conditionally Forward Touch to Parent View
After trying Swapnil Luktuke's answer(to the extent that I understood it, at least) to no avail, and every possible combination of:
- Implementing the methods of
UIGestureRecognizerDelegate
, - Overriding
UITapGestureRecognizer
, - Conditionally calling
ignore(_:for:)
, etc.
(perhaps in my desperation I missed something obvious, but who knows...)
...I gave up and decided to follow the suggestion by @danyapata in the comments to my question, and subclass UITextView.
Partly based on code found on this Medium post, I came up with this UITextView
subclass:
import UIKit
/**
Detects taps on subregions of its attributed text that correspond to custom,
named attributes.
- note: If no tap is detected, the behavior is equivalent to a text view with
`isUserInteractionEnabled` set to `false` (i.e., touches "pass through"). The
same behavior doesn't seem to be easily implemented using just stock
`UITextView` and gesture recognizers (hence the need to subclass).
*/
class LinkTextView: UITextView {
private var tapHandlersByName: [String: [(() -> Void)]] = [:]
/**
Adds a custom block to be executed wjhen a tap is detected on a subregion
of the **attributed** text that contains the attribute named accordingly.
*/
public func addTapHandler(_ handler: @escaping(() -> Void), forAttribute attributeName: String) {
var handlers = tapHandlersByName[attributeName] ?? []
handlers.append(handler)
tapHandlersByName[attributeName] = handlers
}
// MARK: - Initialization
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
commonSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
commonSetup()
}
private func commonSetup() {
self.delaysContentTouches = false
self.isScrollEnabled = false
self.isEditable = false
self.isUserInteractionEnabled = true
}
// MARK: - UIView
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let attributeName = self.attributeName(at: point), let handlers = tapHandlersByName[attributeName], handlers.count > 0 else {
return nil // Ignore touch
}
return self // Claim touch
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesEnded(touches, with: event)
// find attribute name
guard let touch = touches.first, let attributeName = self.attributeName(at: touch.location(in: self)) else {
return
}
// Execute all handlers for that attribute, once:
tapHandlersByName[attributeName]?.forEach({ (handler) in
handler()
})
}
// MARK: - Internal Support
private func attributeName(at point: CGPoint) -> String? {
let location = CGPoint(
x: point.x - self.textContainerInset.left,
y: point.y - self.textContainerInset.top)
let characterIndex = self.layoutManager.characterIndex(
for: location,
in: self.textContainer,
fractionOfDistanceBetweenInsertionPoints: nil)
guard characterIndex < self.textStorage.length else {
return nil
}
let firstAttributeName = tapHandlersByName.allKeys.first { (attributeName) -> Bool in
if self.textStorage.attribute(NSAttributedStringKey(rawValue: attributeName), at: characterIndex, effectiveRange: nil) != nil {
return true
}
return false
}
return firstAttributeName
}
}
As ususal, I'll wait a couple of days before accepting my own answer, just in case something better shows up...
Forward touches other than double tap to UITextView
This could be accomplished by:
- Letting UITextView handle the touch events as suggested in How to ignore touch events and pass them to another subview's UIControl objects?
- In the hitTest of UIView save the reference to the view in which it occurred so that it could be used later
- Add a custom double tap gesture recognizer, that checks if the hitTest reference is not null, indicating that the double tap happened on the target view and forward the event to that view. If not, fail the gesture recognizer. To fail the gesture recognizer you need to include UIKit/UIGestureRecognizerSubclass.h and set the state property to UIGestureRecognizerStateFailed
- Add the gesture recognizer to the UITextView
- Extend the UITextView to override the gestureRecognizers getter and loop through the gesture recognizers to require the custom gesture to fail. This has to be done in the getter as the gesturerecognizers seem to get reset often
how to add double tap gesture to UITextView
I have the solution on iOS6, we can use UIGestureRecognizerDelegate, and override gestureRecognizerShouldBegin: and gestureRecognizer:shouldReceiveTouch:. In this two method, we can check if the gesture is doubleTapGestureForZooming, if not return NO, or return YES. This works perfect in iOS6, but in iOS5 these two delegate method hasn't been called, so iOS5 may need another workaround.
Finally, I get the workaround, we can override the addGestureRecognizer method of UITextView to remove default gesture, wish this will help somebody else.
PS: we really can't remove system gestures of UITextView, we even can't change their property. It seems when event happens, all gestures of UItextview will be added again.
Related Topics
iPhone Camera, How to Avoid Cameraoverlay on Preivew View; How to Know When Entering Preview View
Cameranode Rotate as iOS Device Moving
How to Hide the Home Indicator with Swiftui
Ms Excel Type Spreadsheet Creation Using Objective-C for iOS App
Ios: Helpfulness of Didreceivememorywarning:
How to Create a PDF File Programmatically in an iOS Application
How to Mute/Unmute Audio When Playing Video Using Mpmovieplayercontroller
How to Create Uialertcontroller in Global Swift
How to Use Afnetworking or Sthttprequest to Make a Request of a Soap Web Service
Ios: Usage of Self and Underscore(_) with Variable
How to Share My Current Location to Uiactivityviewcontroller
Stroke Width with a Scenekit Line Primitive Type
How to Do Authorization and Login with Wechat Using the iOS Sdk
How to Get Static Image from Google Maps in iOS
Facebooksdk(4.1.X) Custom Login UI Button - Swift(1.2)
Asynchronous Upload with Nsurlsession Will Not Work But Synchronous Nsurlconnection Does
Using the C API for Imagemagick (On Iphone) to Convert to Monochrome