iOS 13.1 滚动附件时调用 UITextView 委托方法 shouldInteract
iOS 13.1 UITextView delegate method shouldInteract called when scrolling on attachment
我正在使用 UITextView
委托方法做一些自定义工作,例如当用户点击 URL 或附件时打开应用内浏览器:
func textView(_ textView: UITextView,
shouldInteractWith URL: URL,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool
在 iOS 13 中,即使用户只是在 URL 上滚动,也会调用此委托方法,这不是预期的。此行为也适用于图像附件。
现在通过交互调用删除方法。
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1 7.1
* frame #0: 0x0000000104a54c5c ProjectS1`PostListViewController.textView(textView=0x00000001090a4600, URL=Foundation.URL @ 0x000000016b5d1200, characterRange=location=161, length=9, interaction=invokeDefaultAction, self=0x0000000109b03990) at PostListViewController.swift:610:9
frame #1: 0x0000000104a54d70 ProjectS1`@objc PostListViewController.textView(_:shouldInteractWith:in:interaction:) at <compiler-generated>:0
frame #2: 0x00000001b3293eec UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 212
frame #3: 0x00000001b2602160 UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 140
frame #4: 0x00000001b2601f68 UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 100
frame #5: 0x00000001b31dd528 UIKitCore`-[_UITextSimpleLinkInteraction _canBeginInteractionSessionForLinkAtPoint:asTap:] + 136
frame #6: 0x00000001b31dd3d0 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizer:shouldReceiveTouch:] + 228
frame #7: 0x00000001b31dc234 UIKitCore`-[UITextInteraction gestureRecognizer:shouldReceiveTouch:] + 144
frame #8: 0x00000001b2b5f460 UIKitCore`-[UIGestureRecognizer _delegateShouldReceiveTouch:] + 452
frame #9: 0x00000001b2b5edf4 UIKitCore`-[UIGestureRecognizer _shouldReceiveTouch:forEvent:recognizerView:] + 488
frame #10: 0x00000001b2ffa630 UIKitCore`__56-[UITouchesEvent _addGestureRecognizersForView:toTouch:]_block_invoke + 332
frame #11: 0x00000001b2ffa0e4 UIKitCore`__62-[UITouchesEvent _collectGestureRecognizersForView:withBlock:]_block_invoke + 408
frame #12: 0x00000001b2ff9b58 UIKitCore`-[UITouchesEvent _collectGestureRecognizersForView:withBlock:] + 308
frame #13: 0x00000001b2ffa4b0 UIKitCore`-[UITouchesEvent _addGestureRecognizersForView:toTouch:] + 164
frame #14: 0x00000001b2ffa9a8 UIKitCore`-[UITouchesEvent _addTouch:forDelayedDelivery:] + 812
frame #15: 0x00000001b300bfac UIKitCore`_AddTouchToEventAndDetermineIfNeedsCancel + 196
frame #16: 0x00000001b300c074 UIKitCore`____updateTouchesWithDigitizerEventAndDetermineIfShouldSend_block_invoke.96 + 136
frame #17: 0x00000001aef35b20 CoreFoundation`__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #18: 0x00000001aef360e4 CoreFoundation`____NSDictionaryEnumerate_block_invoke.11 + 56
frame #19: 0x00000001aef07a10 CoreFoundation`CFBasicHashApply + 144
frame #20: 0x00000001aef35c80 CoreFoundation`__NSDictionaryEnumerate + 220
frame #21: 0x00000001b300d560 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 2444
frame #22: 0x00000001b30107dc UIKitCore`__handleEventQueueInternal + 4928
frame #23: 0x00000001b3009960 UIKitCore`__handleHIDEventFetcherDrain + 112
frame #24: 0x00000001aee61260 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #25: 0x00000001aee611b4 CoreFoundation`__CFRunLoopDoSource0 + 84
frame #26: 0x00000001aee60920 CoreFoundation`__CFRunLoopDoSources0 + 184
frame #27: 0x00000001aee5b7ec CoreFoundation`__CFRunLoopRun + 1068
frame #28: 0x00000001aee5b098 CoreFoundation`CFRunLoopRunSpecific + 480
frame #29: 0x00000001b8fc5534 GraphicsServices`GSEventRunModal + 108
frame #30: 0x00000001b2f7b7ac UIKitCore`UIApplicationMain + 1940
frame #31: 0x0000000104b090d0 ProjectS1`main at AppDelegate.swift:25:7
frame #32: 0x00000001aecdaf30 libdyld.dylib`start + 4
所以我的问题是,有什么方法可以知道用户是在点击 URL 还是只是在 URL 上滚动?
看起来 textView:shouldInteractWithURL:inRange:interaction:
在正常 link 按下期间最终被调用了 3 次(如果你总是 return YES
)。
几次以查看是否可以调用默认操作(假设这就是 canInvokeDefaultAction
的目的):
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b84d UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 97
frame #4: 0x00007fff477f76f7 UIKitCore`-[_UITextSimpleLinkInteraction _canBeginInteractionSessionForLinkAtPoint:asTap:] + 127
frame #5: 0x00007fff477f75c7 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizer:shouldReceiveTouch:] + 217
frame #6: 0x00007fff477f6506 UIKitCore`-[UITextInteraction gestureRecognizer:shouldReceiveTouch:] + 127
frame #7: 0x00007fff47133669 UIKitCore`-[UIGestureRecognizer _delegateShouldReceiveTouch:] + 493
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b84d UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 97
frame #4: 0x00007fff477f77fb UIKitCore`-[_UITextSimpleLinkInteraction _beginInteractionSessionForLinkAtPoint:asTap:] + 167
frame #5: 0x00007fff477f74d0 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizerShouldBegin:] + 196
frame #6: 0x00007fff477f62a1 UIKitCore`-[UITextInteraction gestureRecognizerShouldBegin:] + 307
frame #7: 0x00007fff471339b6 UIKitCore`-[UIGestureRecognizer _shouldBegin] + 413
最后当手势被识别时:
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b8cc UIKitCore`-[_UITextInteractableItem invokeDefaultAction] + 94
frame #4: 0x00007fff477f6f32 UIKitCore`-[_UITextSimpleLinkInteraction linkTapped:] + 188
frame #5: 0x00007fff4712bbfb UIKitCore`-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 44
滚动时,只有第一个调用发生。
这大概是 iOS 13.1 的变化,它会更快地检查是否可以与 link 进行交互。如果你想让textView:shouldInteractWithURL:inRange:interaction:
有副作用,你只想在手势被真正识别时才做。
我们检查 textView.gestureRecognizers
并且仅在识别出点击手势时才执行自定义操作的方法。
BOOL recognizedTapGesture = NO;
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
if ([recognizer isKindOfClass:UITapGestureRecognizer.class] && recognizer.state == UIGestureRecognizerStateEnded) {
recognizedTapGesture = YES;
break;
}
}
if (!recognizedTapGesture) {
// Tap gesture is not being recognized, this must be an early
// check when touches begin. Leave the link handling alone.
return YES;
}
// Do custom action here
return NO;
我刚刚在 iOS 13 中遇到了同样令人沮丧的问题。这是一个在 Swift 中受 Mihai 上面的回答启发对我有用的修复程序。
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
switch interaction {
case .invokeDefaultAction:
if textView.gestureRecognizers?.contains(where: {[=10=].isKind(of: UITapGestureRecognizer.self) && [=10=].state == .ended}) == true {
// Handle your custom logic here.
return false
}
return true
case .presentActions:
// Default action.
return true
case .preview:
// Default action.
return true
@unknown default:
fatalError()
}
}
我正在使用 UITextView
委托方法做一些自定义工作,例如当用户点击 URL 或附件时打开应用内浏览器:
func textView(_ textView: UITextView,
shouldInteractWith URL: URL,
in characterRange: NSRange,
interaction: UITextItemInteraction) -> Bool
在 iOS 13 中,即使用户只是在 URL 上滚动,也会调用此委托方法,这不是预期的。此行为也适用于图像附件。
现在通过交互调用删除方法。
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1 7.1
* frame #0: 0x0000000104a54c5c ProjectS1`PostListViewController.textView(textView=0x00000001090a4600, URL=Foundation.URL @ 0x000000016b5d1200, characterRange=location=161, length=9, interaction=invokeDefaultAction, self=0x0000000109b03990) at PostListViewController.swift:610:9
frame #1: 0x0000000104a54d70 ProjectS1`@objc PostListViewController.textView(_:shouldInteractWith:in:interaction:) at <compiler-generated>:0
frame #2: 0x00000001b3293eec UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 212
frame #3: 0x00000001b2602160 UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 140
frame #4: 0x00000001b2601f68 UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 100
frame #5: 0x00000001b31dd528 UIKitCore`-[_UITextSimpleLinkInteraction _canBeginInteractionSessionForLinkAtPoint:asTap:] + 136
frame #6: 0x00000001b31dd3d0 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizer:shouldReceiveTouch:] + 228
frame #7: 0x00000001b31dc234 UIKitCore`-[UITextInteraction gestureRecognizer:shouldReceiveTouch:] + 144
frame #8: 0x00000001b2b5f460 UIKitCore`-[UIGestureRecognizer _delegateShouldReceiveTouch:] + 452
frame #9: 0x00000001b2b5edf4 UIKitCore`-[UIGestureRecognizer _shouldReceiveTouch:forEvent:recognizerView:] + 488
frame #10: 0x00000001b2ffa630 UIKitCore`__56-[UITouchesEvent _addGestureRecognizersForView:toTouch:]_block_invoke + 332
frame #11: 0x00000001b2ffa0e4 UIKitCore`__62-[UITouchesEvent _collectGestureRecognizersForView:withBlock:]_block_invoke + 408
frame #12: 0x00000001b2ff9b58 UIKitCore`-[UITouchesEvent _collectGestureRecognizersForView:withBlock:] + 308
frame #13: 0x00000001b2ffa4b0 UIKitCore`-[UITouchesEvent _addGestureRecognizersForView:toTouch:] + 164
frame #14: 0x00000001b2ffa9a8 UIKitCore`-[UITouchesEvent _addTouch:forDelayedDelivery:] + 812
frame #15: 0x00000001b300bfac UIKitCore`_AddTouchToEventAndDetermineIfNeedsCancel + 196
frame #16: 0x00000001b300c074 UIKitCore`____updateTouchesWithDigitizerEventAndDetermineIfShouldSend_block_invoke.96 + 136
frame #17: 0x00000001aef35b20 CoreFoundation`__NSDICTIONARY_IS_CALLING_OUT_TO_A_BLOCK__ + 24
frame #18: 0x00000001aef360e4 CoreFoundation`____NSDictionaryEnumerate_block_invoke.11 + 56
frame #19: 0x00000001aef07a10 CoreFoundation`CFBasicHashApply + 144
frame #20: 0x00000001aef35c80 CoreFoundation`__NSDictionaryEnumerate + 220
frame #21: 0x00000001b300d560 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 2444
frame #22: 0x00000001b30107dc UIKitCore`__handleEventQueueInternal + 4928
frame #23: 0x00000001b3009960 UIKitCore`__handleHIDEventFetcherDrain + 112
frame #24: 0x00000001aee61260 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
frame #25: 0x00000001aee611b4 CoreFoundation`__CFRunLoopDoSource0 + 84
frame #26: 0x00000001aee60920 CoreFoundation`__CFRunLoopDoSources0 + 184
frame #27: 0x00000001aee5b7ec CoreFoundation`__CFRunLoopRun + 1068
frame #28: 0x00000001aee5b098 CoreFoundation`CFRunLoopRunSpecific + 480
frame #29: 0x00000001b8fc5534 GraphicsServices`GSEventRunModal + 108
frame #30: 0x00000001b2f7b7ac UIKitCore`UIApplicationMain + 1940
frame #31: 0x0000000104b090d0 ProjectS1`main at AppDelegate.swift:25:7
frame #32: 0x00000001aecdaf30 libdyld.dylib`start + 4
所以我的问题是,有什么方法可以知道用户是在点击 URL 还是只是在 URL 上滚动?
看起来 textView:shouldInteractWithURL:inRange:interaction:
在正常 link 按下期间最终被调用了 3 次(如果你总是 return YES
)。
几次以查看是否可以调用默认操作(假设这就是 canInvokeDefaultAction
的目的):
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b84d UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 97
frame #4: 0x00007fff477f76f7 UIKitCore`-[_UITextSimpleLinkInteraction _canBeginInteractionSessionForLinkAtPoint:asTap:] + 127
frame #5: 0x00007fff477f75c7 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizer:shouldReceiveTouch:] + 217
frame #6: 0x00007fff477f6506 UIKitCore`-[UITextInteraction gestureRecognizer:shouldReceiveTouch:] + 127
frame #7: 0x00007fff47133669 UIKitCore`-[UIGestureRecognizer _delegateShouldReceiveTouch:] + 493
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b84d UIKitCore`-[_UITextInteractableItem canInvokeDefaultAction] + 97
frame #4: 0x00007fff477f77fb UIKitCore`-[_UITextSimpleLinkInteraction _beginInteractionSessionForLinkAtPoint:asTap:] + 167
frame #5: 0x00007fff477f74d0 UIKitCore`-[_UITextSimpleLinkInteraction interaction_gestureRecognizerShouldBegin:] + 196
frame #6: 0x00007fff477f62a1 UIKitCore`-[UITextInteraction gestureRecognizerShouldBegin:] + 307
frame #7: 0x00007fff471339b6 UIKitCore`-[UIGestureRecognizer _shouldBegin] + 413
最后当手势被识别时:
* frame #0: 0x0000000101610038 Engage`::-[BubbleMessageCell textView:shouldInteractWithURL:inRange:interaction:](self=0x00007ff19e999600, _cmd="textView:shouldInteractWithURL:inRange:interaction:", textView=0x00007ff19eaf2000, url="https://9to5mac.com/2019/09/07/imessage-for-business/", characterRange=location=235, length=33, interaction=UITextItemInteractionInvokeDefaultAction) at BubbleMessageCell.mm:623:5
frame #1: 0x00007fff478b2902 UIKitCore`-[UITextView _allowInteraction:forTextInteractableItem:] + 532
frame #2: 0x00007fff46b8ba1c UIKitCore`-[_UITextInteractableItem _allowInteraction:] + 135
frame #3: 0x00007fff46b8b8cc UIKitCore`-[_UITextInteractableItem invokeDefaultAction] + 94
frame #4: 0x00007fff477f6f32 UIKitCore`-[_UITextSimpleLinkInteraction linkTapped:] + 188
frame #5: 0x00007fff4712bbfb UIKitCore`-[UIGestureRecognizerTarget _sendActionWithGestureRecognizer:] + 44
滚动时,只有第一个调用发生。
这大概是 iOS 13.1 的变化,它会更快地检查是否可以与 link 进行交互。如果你想让textView:shouldInteractWithURL:inRange:interaction:
有副作用,你只想在手势被真正识别时才做。
我们检查 textView.gestureRecognizers
并且仅在识别出点击手势时才执行自定义操作的方法。
BOOL recognizedTapGesture = NO;
for (UIGestureRecognizer *recognizer in textView.gestureRecognizers) {
if ([recognizer isKindOfClass:UITapGestureRecognizer.class] && recognizer.state == UIGestureRecognizerStateEnded) {
recognizedTapGesture = YES;
break;
}
}
if (!recognizedTapGesture) {
// Tap gesture is not being recognized, this must be an early
// check when touches begin. Leave the link handling alone.
return YES;
}
// Do custom action here
return NO;
我刚刚在 iOS 13 中遇到了同样令人沮丧的问题。这是一个在 Swift 中受 Mihai 上面的回答启发对我有用的修复程序。
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
switch interaction {
case .invokeDefaultAction:
if textView.gestureRecognizers?.contains(where: {[=10=].isKind(of: UITapGestureRecognizer.self) && [=10=].state == .ended}) == true {
// Handle your custom logic here.
return false
}
return true
case .presentActions:
// Default action.
return true
case .preview:
// Default action.
return true
@unknown default:
fatalError()
}
}