Rich
Rich

Reputation: 83

Custom iOS Keyboard – Keys Are Too Slow

I'm working on a custom iOS keyboard and cannot seem to speed up the response on the keys. The problem I'm having is that the keys respond to touchUpInside events with a small but significant delay. The standard iOS keyboard (and most custom keyboards I've downloaded) respond immediately. The short delay I'm experiencing leads to significant usability issues. The delay is barely noticeable in the simulator, but on the device (iPhone), it causes keys to be be missed when you're typing quickly.

I've tried a variety of techniques to resolve the speed issues:

How can I speed up the keys for the keyboard? Most of the code for the keyboard is below (I've only included the function for the QWERTY keyboard, but similar functions exist for the numeric keypad and the symbols keypad)

To create the project, I simply created a new iOS project, added an extension for the keyboard, and wrote the class below:

    import UIKit

    final class KeyboardViewController: UIInputViewController {

var screenWidth : CGFloat = CGFloat()
var capsLockOn : Bool = false

override func viewDidLoad() {
    super.viewDidLoad()

    // Perform custom UI setup here
    view.backgroundColor = UIColor(red: 209 / 255, green: 213 / 255, blue: 219 / 255, alpha: 1)

    showQWERTYKeyboard()
}

func showQWERTYKeyboard() {

    //Setup some constants and variables that will be used for layout
    var buttonWidth : CGFloat = 26
    let buttonHeight : CGFloat = 40
    let standardButtonWidth : CGFloat = buttonWidth
    let horizontalSpaceBetweenButtons : CGFloat = 6.0
    let verticalSpaceBetweenButtons : CGFloat = 18

    var nextY : CGFloat = 0 //Tracks the current vertical position so we know where to place the next button
    var nextX : CGFloat = 1.5 //Tracks the current horizontal position so we know where to place the next button
    let standardNextX : CGFloat = nextX //Placeholder for the "nextX" value for the left-most key
    var buttonStrings : [String] = [""] //Holds the row of keys that get put on the screen

    //This outer loop iterates over each of the 4 rows. The inner loop below iterates over each letter in the array.
    var rowCounter : Int = Int()
    for rowCounter = 0; rowCounter<4; rowCounter++ {

        switch rowCounter {
        case 0:
            if capsLockOn {
                buttonStrings = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
            }
            else {
                buttonStrings = ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"]
            }
            nextY = 4
            nextX = standardNextX
        case 1:
            if capsLockOn {
                buttonStrings = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
            }
            else {
                buttonStrings = ["a", "s", "d", "f", "g", "h", "j", "k", "l"]
            }
            nextY = (CGFloat(rowCounter) * buttonHeight) + CGFloat((rowCounter))*verticalSpaceBetweenButtons
            nextX = (((buttonWidth + horizontalSpaceBetweenButtons) * 1) + (2*4))/2
        case 2:
            if capsLockOn {
                buttonStrings = ["^^", "Z", "X", "C", "V", "B", "N", "M", "<<"]
            }
            else {
                buttonStrings = ["^^", "z", "x", "c", "v", "b", "n", "m", "<<"]
            }
            nextY = (CGFloat(rowCounter) * buttonHeight) + CGFloat((rowCounter))*verticalSpaceBetweenButtons
            nextX = standardNextX
        case 3:
            buttonStrings = ["8", "kb", "sp", "rt"]
            nextY = (CGFloat(rowCounter) * buttonHeight) + CGFloat((rowCounter))*verticalSpaceBetweenButtons
            nextX = standardNextX
        default:
            break
        }

        //This inner loop iterates the current buttonStrings array and adds a target to the buttons, styles the buttons and adds the buttons to the view
        for buttonString in buttonStrings {
            //Create a new button
            let button : UIButton = UIButton(type: UIButtonType.Custom)

            styleAButton(button, buttonTitle: buttonString) //Customize the look and feel of the button

            switch buttonString {
            case "sp":
                button.addTarget(self, action: "touchUpInsideSpace:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = (standardButtonWidth + horizontalSpaceBetweenButtons) * 5
            case "kb", "KB":
                button.addTarget(self, action: "advanceToNextInputMode", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth * 1.33
            case "rt":
                button.addTarget(self, action: "touchUpInsideReturn:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth * 2.55
            case "^^":
                button.addTarget(self, action: "touchUpInsideCapsLock:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth * 1.33
            case "<<":
                button.addTarget(self, action: "touchUpInsideBackspace:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth * 1.33
                nextX = nextX + 7
            case "8":
                button.addTarget(self, action: "touchUpInsideNumbers:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth * 1.33
            case "z", "Z": //Special case to simplify layout of the caps lock button
                button.addTarget(self, action: "touchUpInsideLetter:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth
                nextX = nextX + 7
            default:
                button.addTarget(self, action: "touchUpInsideLetter:", forControlEvents: UIControlEvents.TouchUpInside)
                buttonWidth = standardButtonWidth
            }

            //Make the button
            button.frame = CGRectMake(nextX, nextY, buttonWidth, buttonHeight)

            //Determine horizontal placement for the next key
            nextX = nextX + horizontalSpaceBetweenButtons + buttonWidth

            //Add button to the view
            self.view.addSubview(button)
        }
    }
}
    func styleAButton(button : UIButton, buttonTitle : String) {



    //Configure button look and feel
    button.backgroundColor = UIColor.whiteColor()
    button.setTitle(buttonTitle, forState: UIControlState.Normal)
    button.setTitleColor(UIColor.blackColor(), forState: UIControlState.Normal)
    button.titleLabel?.font = UIFont.systemFontOfSize(23.0)

    button.layer.cornerRadius = 4.0
    button.layer.masksToBounds = true
    button.layer.shadowColor = UIColor.lightGrayColor().CGColor
    button.layer.shadowOpacity = 0.8
    button.layer.shadowRadius = 6.0
    button.layer.shadowOffset = CGSizeMake(1.0, 1.0)
    button.contentEdgeInsets = UIEdgeInsetsMake(-3, -3, -3, -3)

}

//Shows the QWERTY keyboard
func touchUpInsideLetters(sender : UIButton) {
    for someView in view.subviews {
        someView.removeFromSuperview()
    }
    showQWERTYKeyboard()
}

//Shows the "numbers" keyboard
func touchUpInsideNumbers(sender : UIButton) {
    for someView in view.subviews {
        someView.removeFromSuperview()
    }
    showNumericKeyboard()
}

//Shows the keyboard with characters like [ ] { } etc.
func touchUpInsideMoreSymbols(sender : UIButton) {
    for someView in view.subviews {
        someView.removeFromSuperview()
    }
    showMoreSymbolsKeyboard()
}

//inserts the character of the key being pressed
func touchUpInsideLetter(sender : UIButton) {
    self.textDocumentProxy.insertText((sender.titleLabel?.text)!)

}

//inserts a space character
func touchUpInsideSpace(sender : UIButton) {
    let textToInsert : String = " "
    self.textDocumentProxy.insertText(textToInsert)

}

//inserts a new line character
func touchUpInsideReturn(sender : UIButton) {
    let textToInsert : String = "\n"
    self.textDocumentProxy.insertText(textToInsert)

}

//Toggles the global capsLockOn variable to be true or false (depending on current state).
func touchUpInsideCapsLock(sender : UIButton) {
    capsLockOn = !capsLockOn
    showQWERTYKeyboard()

}

//Inserts a backspace
func touchUpInsideBackspace(sender : UIButton) {
    (textDocumentProxy as UIKeyInput).deleteBackward()
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated
}
    } //End of class

Thanks for any help you can offer. I'm new to iOS development and I'm hopeful that I'm just missing something obvious.

Upvotes: 2

Views: 1364

Answers (2)

Mundi
Mundi

Reputation: 80265

Perhaps if touchupInside take too long you might consider the touchDown event instead?

Upvotes: 0

Rich
Rich

Reputation: 83

I just figured it out! It's surprising to learn that drawing a shadow is very expensive unless you configure the shadowPath property. I wasn't doing that in my "styleAButton" method, so I added it like this:

    button.layer.shadowPath = UIBezierPath(rect: button.bounds).CGPath

For more detail, here's a post by someone that discusses what's going on behind the scenes a bit.

I was able to isolate the issue by guessing that perhaps it had to do with styling the button. That didn't make a lot of sense to me at the time, but now I understand that because the button is animated on a key press, it's doing some drawing on that shadow and since it's an expensive operation, it was slowing the insert of the character (it was probably blocking the main thread).

Upvotes: 4

Related Questions