Reputation: 83
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
Reputation: 80265
Perhaps if touchupInside
take too long you might consider the touchDown
event instead?
Upvotes: 0
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