4

I have a problem with xcode 8.1/swift 3. I think it might be a bug in the debugger XCode software but thought I would see if anybody sees something I am missing. I am working on an ios application where I need to override the backspace method used with UITextField. I setup a delegate to watch for the backspace key being pressed. The program works until I set a debugger breakpoint in the overridden method (see code below). When I do this the backspace method gets called twice for each time I press the key instead of just one time. If I delete breakpoint the code works. Is this just a bug or am I doing something wrong? Here is the code thanks for your help!

import UIKit

class ViewController: UIViewController, EqFieldDelegate {

override func viewDidLoad() {
    super.viewDidLoad()

    let newField = EqField()
    newField.myDelegate = self
    newField.becomeFirstResponder()
    view.addSubview(newField)
    newField.frame = CGRect(x: 50, y: 50, width: 200, height: 150)

}

func backspacePressed( ){

    print("in backspacePressed") // breakpoint added here
}

}   

Here is the subclass of UITextField:

import UIKit

class EqField: UITextField {

    var myDelegate: EqFieldDelegate?

    override func deleteBackward() {
        super.deleteBackward()
        myDelegate?.backspacePressed()
    }
}

protocol EqFieldDelegate {
    func backspacePressed()
}
charmingToad
  • 1,597
  • 11
  • 18
siege097
  • 83
  • 7
  • What makes you think it's calling twice? When hits the breakpoint and you hit continue it hits it again? Or what specifically? – TheValyreanGroup Nov 17 '16 at 21:41
  • I made a test project with the provided code and can verify - not only does the breakpoint get hit twice, the log prints twice as well. Without the breakpoint, the log prints once. – charmingToad Nov 18 '16 at 00:47
  • It sometimes happens with XCode debugger. Debugger sometimes misbehaved when working with UI elements and their delegates. – Ankit Thakur Nov 18 '16 at 03:26
  • As already noted the code is getting called twice. In the actual program I am working on two things get deleted instead of one. – siege097 Nov 18 '16 at 04:29

1 Answers1

1

I have a theory about why this is happening.

To illustrate, imagine we're making our own back button. It should have the following behavior to mimic Apple's:

  1. On touch down, call backspacePressed
  2. After .5 seconds, if touch is still down, call backspacePressed again
  3. After every .1 seconds, if touch is still down, call backspacePressed again

Here's how I would implement it:

  1. Add this to the end of your viewDidLoad

    let button = UIButton(type: .system)
    button.setTitle("Tap Me", for: .normal)
    button.addTarget(self, action: #selector(ViewController.handleButtonTouched(button:)), for: .touchDown)
    button.addTarget(self, action: #selector(ViewController.handleButtonReleased(button:)), for: .touchUpInside)
    button.frame = CGRect(x: 50, y: newField.frame.maxY, width: 200, height: 150)
    view.addSubview(button)
    
  2. Add this after viewDidLoad

    var buttonTimer: Timer?
    
    func handleButtonTouched(button: UIButton) {
        buttonTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false, block: { [weak self] timer in
    
            print("button's been held down for a while!")
            self?.backspacePressed()
    
            self?.buttonTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { (timer) in
                print("button's still held down")
                self?.backspacePressed()
            })
    
        })
        print("button was pressed") // add a breakpoint here
        self.backspacePressed()
    }
    
    
    func handleButtonReleased(button: UIButton) {
        buttonTimer?.invalidate()
    }
    
  3. (Optional) Comment out your log so your console matches mine for this experiment

    func backspacePressed( ){
        //print("in backspacePressed")
    } 
    

Without a breakpoint, if you tap and hold this button, you'll get logs printed following the behavior I outlined above:

button was pressed
button's been held down for a while!
button's still held down
button's still held down
button's still held down
button's still held down

If you just tap the button (don't hold it), you'll just get

button was pressed

However, if you set the breakpoint, tap the button, and wait for a second after the breakpoint hits before pressing continue, you get two logs:

button was pressed
button's been held down for a while!

You'd think, since you didn't hold the button for long enough, you wouldn't get the second log. What's happening is the timer is ticking down even though you're stopped on a breakpoint, and when execution returns to the program, the result of the timer is evaluated before the button release is evaluated.

I'm not exactly sure why things happen in this order - why can't the button release be handled before the timer event? But you can see that in normal execution without a breakpoint, it's not even a question of what happens first - a short button tap will cause handleButtonReleased to be called before the timer can go off.

I suspect that in your case, Apple's keyboard is doing something similar, and you're setting a breakpoint that's holding your touch in stasis while a timer somewhere decides that yes, this is indeed a long press! Even though it really isn't. I don't have a workaround, but hopefully this shines some light on what's happening.

charmingToad
  • 1,597
  • 11
  • 18
  • Thanks for your comments and insights, perhaps they will lead to a work around. I do not think this was happening with xcode 7, so it must be something new they introduced, but I do not have this version to test. I was really looking to verify that my delegate was setup correctly or if something had changed in swift 3 that I was not aware of. I guess I will use print statements to the console for now for debugging:( – siege097 Nov 18 '16 at 04:16
  • Just FYI - on my machine, this happens in both Xcode 7.3.1 as well as Xcode 8.1. I think your delegate is set up correctly and you should continue to use breakpoints to debug, but write your code to ignore backspace events if one is already being handled. – charmingToad Nov 18 '16 at 04:34