2

After updating to Xcode 9.3.1, I've encountered a Thread BAD_ACCESS crash when the keyboard appears by manually making a UITextView becomeFirstResponder.

To be clear, this has previously worked just before updating Xcode to 9.3.1 but now I can't figure out why the crash occurs.

I've made a stripped down example to showcase this. There are no other variables or functions in the UIViewController I've provided.

class ErrorTestsController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        setupUIElements()
    }

    private func setupUIElements() {
        view.backgroundColor = UIColor.red
        view.addSubview(blankTextView)

        blankTextView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        blankTextView.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
        blankTextView.heightAnchor.constraint(equalToConstant: 40.0).isActive = true

        blankTextViewBottomAnchor = blankTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0.0)
        blankTextViewBottomAnchor?.isActive = true
        // Whether I use safeAreaLayoutGuide or the regular view's bottomAnchor changes nothing

        // Add observers to move the blankTextView up when the keyboard appears
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

        // add a UITapGestureRecognizer to make the blankTextView becomeFirstResponder and show the keyboard (will also trigger the above NotificationObserver)
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(showAddTextView)))
    }

    // Define reference to bottomAnchor of the UITextView called "blankTextView" (using iOS 9.0 + NSLayoutConstraints)
    public var blankTextViewBottomAnchor: NSLayoutConstraint?

    public var blankTextView: UITextView = {
        let textview = UITextView(frame: .zero, textContainer: nil)
        textview.translatesAutoresizingMaskIntoConstraints = false
        textview.backgroundColor = UIColor.black
        return textview
    }()

    @objc private func showAddTextView() {
        blankTextView.becomeFirstResponder()
    }

    @objc private func keyboardWillShow(notification: NSNotification) {
        guard
            let keyboardFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue,
            let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
            else { return }

        blankTextViewBottomAnchor?.constant = -keyboardFrame.height

        UIView.animate(withDuration: keyboardDuration) {
            self.view.layoutIfNeeded()
        }
    }

    @objc private func keyboardWillHide(notification: NSNotification) {
        guard
            let keyboardDuration = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as AnyObject).doubleValue
            else { return }

        blankTextViewBottomAnchor?.constant = 0.0

        UIView.animate(withDuration: keyboardDuration) { 
            self.view.layoutIfNeeded()
        }
    }
}

Here is the crash output:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d0224560'
*** First throw call stack:
(0x181c76d8c 0x180e305ec 0x181c84098 0x181c7c5c8 0x181b6241c 0x18b8351bc 0x18bdb588c 0x18b9c1310 0x18b9c132c 0x18b9c132c 0x18b9c0eec 0x18b9c0850 0x18b9b9934 0x18b84f30c 0x18b9b0330 0x18b8f1b30 0x18b8f1f4c 0x18bac6304 0x100979324 0x100979368 0x18ba26750 0x18bf932a4 0x18bb88e6c 0x18ba257a8 0x18bf84ac4 0x18ba1f540 0x18ba1f078 0x18ba1e8dc 0x18ba1d238 0x18c1fec0c 0x18c2011b8 0x18c201518 0x18c1fa258 0x181c1f404 0x181c1ec2c 0x181c1c79c 0x181b3cda8 0x183b1f020 0x18bb1d78c 0x10089e748 0x1815cdfc0)
libc++abi.dylib: terminating with uncaught exception of type NSException

This crash happens whether I use the UITapGestureRecognizer on the self.view or if I manually tap the UITextView itself. Both result in the same crash.

NOTE: On some occasions, I've observed that the actual crash output is different. In this case it displays "[__NSCFString isHidden]:". In other cases, the output was "[__NSArrayI position]:". However, I did not change the code at any time.

But it is always some form of EXC_BAD_ACCESS.

EDIT: FULL STACK TRACE

2018-05-17 12:41:23.714232-0400 TESTAPP[4368:1363924] [Application] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?

2018-05-17 12:41:25.497939-0400 TESTAPP[4368:1363924] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-05-17 12:41:25.500096-0400 TESTAPP[4368:1363924] [MC] Reading from public effective user settings.
2018-05-17 12:41:25.514580-0400 TESTAPP[4368:1363924] -[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d023ec00
2018-05-17 12:41:25.515767-0400 TESTAPP[4368:1363924] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString isHidden]: unrecognized selector sent to instance 0x1d023ec00'
*** First throw call stack:
(0x181c76d8c 0x180e305ec 0x181c84098 0x181c7c5c8 0x181b6241c 0x18b8351bc 0x18bdb588c 0x18b9c1310 0x18b9c132c 0x18b9c132c 0x18b9c0eec 0x18b9c0850 0x18b9b9934 0x18b84f30c 0x18b9b0330 0x18b8f1b30 0x18b8f1f4c 0x18bac6304 0x100fa58cc 0x100fa5910 0x18ba26750 0x18bf932a4 0x18bb88e6c 0x18ba257a8 0x18bf84ac4 0x18ba1f540 0x18ba1f078 0x18ba1e8dc 0x18ba1d238 0x18c1fec0c 0x18c2011b8 0x18c201518 0x18c1fa258 0x181c1f404 0x181c1ec2c 0x181c1c79c 0x181b3cda8 0x183b1f020 0x18bb1d78c 0x100ecad04 0x1815cdfc0)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb) 

Edit 2: This is all done programmatically. Not using storyboards. In my AppDelegate, I've define the UIWindow and its rootView like so:

    window = UIWindow(frame: UIScreen.main.bounds)
    window?.makeKeyAndVisible()

    let navigationController = UINavigationController(rootViewController: ErrorTestsController())
    window?.rootViewController = navigationController
Chrishon Wyllie
  • 209
  • 1
  • 2
  • 11
  • You don't remove the observation of Notification? – Larme May 17 '18 at 16:18
  • @Larme Removing the observation changes nothing as well. In fact, if I commented out the notification altogether, the crash still occurs. – Chrishon Wyllie May 17 '18 at 16:21
  • Try calling it from will/didAppear. – CodeBender May 17 '18 at 16:23
  • Can you include the full stack trace? I can't see anything that would make `blankTextView` become a zombie, so it could be happening on something internal to the text view. – Brian Nickel May 17 '18 at 16:31
  • @matt The UIView.animate statements are necessary to animate the NSLayoutConstraint at the same time interval as the keyboard animation time. Also calling self.view.layoutIfNeeded allows for a smooth animation of the NSLayoutConstraint. – Chrishon Wyllie May 17 '18 at 16:43
  • @BrianNickel Added full stack trace per your request. – Chrishon Wyllie May 17 '18 at 16:46
  • To add the requisite "Did you plug it in" sort of comment... Have you tried CLEAN? That fixes an amazing number of things. – Terry May 17 '18 at 16:50
  • @Terry maybe 100 times at this point... I've used implementations like this verrryyy often. It just so happens that updating Xcode 9.3.1 two days ago broke this otherwise "no-brainer" function. – Chrishon Wyllie May 17 '18 at 16:53
  • @matt see edit number two – Chrishon Wyllie May 17 '18 at 16:57
  • @matt By the way, I set a initial view controller as you suggested in storyboard (which is virtually blank aside from the default UIViewController) and this also did not solve the crash. I believe that warning is somewhat irrelevant for this case since I'm doing an all programmatic approach. – Chrishon Wyllie May 17 '18 at 17:03
  • @matt I'm guessing you meant `didFinishLaunchingWithOptions`. Either way, I've set the initial view controller, changed the info.plist setting but neither did nothing to fix the problem. By "fix" the didFinishLaunchingWithOptions, what did you mean exactly? – Chrishon Wyllie May 17 '18 at 17:31
  • Do you still get the crash if you don't listen to the keyboard notifications? Is it the becomeFirstResponder part that is crashing, or does the crash occur when you animate your constraint? – rodskagg May 17 '18 at 18:09
  • Is this crashing on device, simulator or both? I've tried with [this minimal app delegate](https://gist.github.com/bnickel/b26a1707c02af3cf00b39cfeb69afc61) and I'm not getting that crash in the 8 or the X simulator running 11.3 (15E217) – Brian Nickel May 17 '18 at 18:17

1 Answers1

-1

I've figured it out....

I want to start by saying the solution is NOT within the scope of the question but I figured I should post it anyway as it can unknowingly affect other UI Elements that appear to be related to it, thereby directly causing my issue.

To begin, elsewhere, I defined an extension for UIView like so...

extension UIView {

    func doSomething() {

        // Attempt to be sure that sublayers exist first...
        if (self.layer.sublayers != nil) && (self.layer.sublayers.count > 0) {

            self.layer.sublayers?.removeAll() 
            // These also cause crash
            // self.layer.sublayers?.removeFirst() 
            // self.layer.sublayers?.forEach { $0.removeFromSuperlayer() } 
            // self.layer.sublayers = nil
        }

        // I insert a CALayer at index 0 with:
        self.layer.insertSublayer(someCALayer, at: 0)
    }
}

EXPLANATION:

At some point, I manually add a CALayer to whichever UIView calls this extended function (not the self.view of a UIViewController but a custom UIView). I had a requirement where I needed to replace this sublayer with another CALayer, basically starting from scratch. So I cleared the current one if there was one.

So for example, imagine I defined another UIView called someBlankUIView. This UIView has no subviews in it, but I call this doSomething() function on it like so: someBlankUIView.doSomething() after adding it as subview and waiting for the UIViewController to call viewDidLayoutSubviews(). This does NOT cause a crash immediately and works as intended.

HOWEVER, the following actions will cause a crash:
- once you make the keyboard appear with becomFirstResponder()
- tap a UITextField/UITextView
- call a function like layoutIfNeeded(), setNeedsLayout(), etc. on the self.view of the UIViewController

KNOWN CRASHES: If this removal of all sublayers is left in, the following will result in multiple kinds of crashes. Examples include:
EXC_BAD_ACCESS
NSArrayI position
om_wf isHidden
__NSCFString isHidden
-__NSOrderedSetM isHidden:

It must be noted that some of these have the same isHidden keyword but at the same time, these errors are displayed seemingly at random as the code does not change between crashes but a different reason is displayed in the log.

SOLUTION:

Simply never remove a layer from the sublayers? Even if you have manually inserted one. This sounds somewhat unusual and maybe requires more research, perhaps.

But again like I said, this solution may be out of the scope of the original question but I thought I should post it since it directly causes the crash.

FURTHER NOTES:

Also to clarify, this likely has nothing to do with updating Xcode 9.3.1. From experience updating always breaks something for me and this time, I assumed it was the case again since I updated immediately after adding this single line of code. (I had to update Xcode since I updated my iPhone which caused compatibility issues with my then-current version of Xcode). Hope I was descriptive enough.

Chrishon Wyllie
  • 209
  • 1
  • 2
  • 11
  • This did help me get to the area of my problem! I got this when i would remove all of the sublayers in a view, and it did cause these crashes that were hard to pin down. my solution however was not to never remove sublayers, but to only remove those i added in. – Pasosta Mar 29 '19 at 18:40