Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App crash with voice over whenever a textview with a link in it is activated

Stack trace:

* thread #1: tid = 0x1ee50f, 0x00000001096f5d05 libswiftFoundation.dylib`static Foundation.DateComponents._unconditionallyBridgeFromObjectiveC (Swift.Optional<__ObjC.NSDateComponents>) -> Foundation.DateComponents with unmangled suffix "_merged" + 85, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x00000001096f5d05 libswiftFoundation.dylib`static Foundation.DateComponents._unconditionallyBridgeFromObjectiveC (Swift.Optional<__ObjC.NSDateComponents>) -> Foundation.DateComponents with unmangled suffix "_merged" + 85
frame #1: 0x000000010558e36f Invest`@objc InvestDashboard.textView(UITextView, shouldInteractWith : URL, in : _NSRange) -> Bool + 79 at InvestDashboard.swift:0
frame #2: 0x000000011fd478fc UIKit`-[UITextViewAccessibility accessibilityActivate] + 838
frame #3: 0x000000011fed29d2 UIAccessibility`-[NSObject(UIStorage) accessibilityPerformAction:withValue:fencePort:] + 1448
frame #4: 0x000000011feaa63d UIAccessibility`_performActionCallback + 163
frame #5: 0x000000011fc0cec4 AXRuntime`_AXXMIGPerformAction + 107
frame #6: 0x000000011fc06f06 AXRuntime`_XPerformAction + 216
frame #7: 0x000000011fc16541 AXRuntime`mshMIGPerform + 266
frame #8: 0x0000000106d1ff89 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
frame #9: 0x0000000106d1ff01 CoreFoundation`__CFRunLoopDoSource1 + 465
frame #10: 0x0000000106d18045 CoreFoundation`__CFRunLoopRun + 2389
frame #11: 0x0000000106d17494 CoreFoundation`CFRunLoopRunSpecific + 420
frame #12: 0x000000010cc38a6f GraphicsServices`GSEventRunModal + 161
frame #13: 0x00000001078c2964 UIKit`UIApplicationMain + 159
frame #14: 0x000000010467e99f InvestDemo`main + 111 at AppDelegate.swift:29
frame #15: 0x000000010b88268d libdyld.dylib`start + 1
frame #16: 0x000000010b88268d libdyld.dylib`start + 1

This happens whenever I use activate on a TextView that has a link in it. I've tried a lot of different things, like overriding accessibilityActivate() -> Bool but the app crashes before this method ever gets called. Any suggestions?

like image 370
Alex S Avatar asked Apr 03 '17 16:04

Alex S


1 Answers

I solved this issue and wanted to share so that others with the same confusing problem can fix it.

The first thing to do is to subclass UITextView and in that subclass override func accessibilityActivate() -> Bool. Then make a delegate that handles what happens when the TextView is activated and call it through the overridden method.

Then set the delegate of the TextView based on UIAccessibilityIsVoiceOverRunning(), so if voice over is running then set the UITextViewDelegate to nil which prevents the crash from happening and then the activation action is handled by the method you overrode in the subclass above. Finally, set up a listener for UIAccessibilityVoiceOverStatusChanged and set the UITextViewDelegate to nil when going from off to on, and setting it to the original delegation class for the opposite scenario.

Some code:

//: Playground - noun: a place where people can play

import UIKit

class Test : UITabBarController, UITextViewDelegate, ActivationDelegate {

    var voiceOverRunning : Bool {
        get {
            return UIAccessibilityIsVoiceOverRunning()
        }
    }

    var testView = AccessibilityView()

    override func viewDidLoad() {
        if !voiceOverRunning {
            testView.delegate = self
        } else {
            testView.activationDelegate = self
        }

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(self.didChangeVoiceOver),
            name: NSNotification.Name(rawValue: UIAccessibilityVoiceOverStatusChanged),
            object: nil)
    }

    func doStuff() {
        /*
         Do what you want to happen here on activation
         */
    }


    @objc
    func didChangeVoiceOver(){
        testView.delegate              = voiceOverRunning ? nil : self
        testView.activationDelegate    = voiceOverRunning ? self : nil
    }


}

protocol ActivationDelegate : class {
    func doStuff()
}
class AccessibilityView : UITextView {

    var activationDelegate : ActivationDelegate!

    override func accessibilityActivate() -> Bool {
        activationDelegate.doStuff()
        return true
    }
}
like image 79
Alex S Avatar answered Sep 27 '22 22:09

Alex S