Touch based input may be the predominant form of interaction for mobile apps, but a surprising number of users (especially on tablets) also use a keyboard. Ever since iOS 7 became available, Apple has provided some support for adding keyboard shortcuts to developers’ iOS apps. Some controls can benefit from implementing keyboard shortcuts to make a user’s life much easier by providing convenient way to enter text and navigate. In this article we’ll take a look at using the UIKeyCommand class to implement keyboard shortcuts for an iOS FlexGrid.

iOS Key Commands

The UIKeyCommand class allows you to specify keys pressed (including modifier keys) and a resulting action to take. Implementing a key command requires we specify the key, and modifier keys (command in this case), the method we’ll call when the key is pressed, and (on iOS 9+) a discoverability title:


[UIKeyCommand keyCommandWithInput:@"f" modifierFlags:UIKeyModifierCommand action:@selector(filterKeyPress) discoverabilityTitle:@"Filter"]  

Some key presses (such as the Enter\Return key) requires we provide an escaped string, such as a carriage return:


[UIKeyCommand keyCommandWithInput:@"\\r" modifierFlags:0 action:@selector(returnKeyPress) discoverabilityTitle:@"Edit"]  

Other key commands are already available as constants, such as the arrow keys:


[UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(leftKeyPress) discoverabilityTitle:@"Left"],  


You’ll need to implement these UIKeyCommands inside the keyCommands method, which is where you can specify all of the keyboard shortcuts you wish to add to your app. Our full implementation will have many shortcuts:


- (NSArray *)keyCommands {  
    return @[  
            [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(leftKeyPress) discoverabilityTitle:@"Left"],  
            [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:0 action:@selector(rightKeyPress) discoverabilityTitle:@"Right"],  
            [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(upKeyPress) discoverabilityTitle:@"Up"],  
            [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(downKeyPress) discoverabilityTitle:@"Down"],  
            [UIKeyCommand keyCommandWithInput:@"\\r" modifierFlags:0 action:@selector(returnKeyPress) discoverabilityTitle:@"Edit"],  
            [UIKeyCommand keyCommandWithInput:@"f" modifierFlags:UIKeyModifierCommand action:@selector(filterKeyPress) discoverabilityTitle:@"Filter"]  
    ];  
}  

Providing a discoverability title in iOS 9+ allows your list of key commands to show up when the user holds down the command key. keyboardshortcuts

Handling Keyboard Navigation

Using keyboard shortcuts to navigate through around the FlexGrid requires that we keep track of the current row and column that is selected. Moving around the grid will be accomplished by incrementing and decrementing the last row and column that was select, and using the selectCellRange to move the selection. For instance, we can move right by incrementing the column selection by one, and moving to the new position using selectCellRange. First, we’ll create a GridCellRange object that contains the currently selected row and column.


GridCellRange *range;  

Next, we’ll need to initialize the range Object in viewDidLoad to our starting position (which is all zeros for the first cell), and select it:


    range = [[GridCellRange alloc] initWithRow:0 col:0 row2:0 col2:0];  
    [grid selectCellRange:[[GridCellRange alloc] initWithRow:0 col:0 row2:0 col2:0] show:true];  

Now we’ll need to handle all of the individual key presses. As we mentioned earlier, this is largely a matter of incrementing or decrementing a row or column and then moving the selected cell to this updated position. We’re also canceling out of edit mode if the grid is currently editing a cell.


- (void) leftKeyPress {  
    if(inEditMode == true){  
        [grid finishEditing:true];  
        inEditMode = false;  
    }  
    if(range.col > 0){  
        range.col--;  
        range.col2--;  
        //[grid selectCellRange:range];  
        [grid selectCellRange:[[GridCellRange alloc] initWithRow:range.row col:range.col row2:range.row2 col2:range.col2]];  
    }  
}  

- (void) rightKeyPress {  
    if(inEditMode == true){  
        [grid finishEditing:true];  
        inEditMode = false;  
    }  
    if(range.col < (grid.columns.count - 1)){  
        range.col++;  
        range.col2++;  
        //[grid selectCellRange:range];  
        [grid selectCellRange:[[GridCellRange alloc] initWithRow:range.row col:range.col row2:range.row2 col2:range.col2]];  
    }  
}  

- (void) upKeyPress {  
    if(inEditMode == true){  
        [grid finishEditing:true];  
        inEditMode = false;  
    }  
    if(range.row != 0){  
        range.row--;  
        range.row2--;  
        //[grid selectCellRange:range];  
        [grid selectCellRange:[[GridCellRange alloc] initWithRow:range.row col:range.col row2:range.row2 col2:range.col2]];  
    }  
}  

- (void) downKeyPress {  
    if(inEditMode == true){  
        [grid finishEditing:true];  
        inEditMode = false;  
    }  
    if(range.row < (grid.rows.count - 1)){  
        range.row++;  
        range.row2++;  
        //[grid selectCellRange:range];  
        [grid selectCellRange:[[GridCellRange alloc] initWithRow:range.row col:range.col row2:range.row2 col2:range.col2]];  
    }  
}  

Handling other Key Presses and Events

There are a few other functions you’ll likely want to add to your application. iOS handles copy, paste, and cut, so you don’t need to explicitly add those (though the full sample includes the explicit implementations if you’re curious). However, we should handle being able to enter and exit edit mode using the enter\return key. We can use a Boolean value (called inEditMode in the below code) to track as the user toggles edit mode on and off.


- (void) returnKeyPress {  
    if(inEditMode == false){  
        [grid startEditing:true row:range.row column:range.col];  
        inEditMode = true;  
    }  
    else{  
        [grid finishEditing:false];  
        inEditMode = false;  
    }  

}  

We should also handle updating our range and canceling edit mode when a user taps on a cell rather than uses keyboard entry to navigate to it. We’ll implement a few methds from the FlexGridDelegate to handle this:


-(bool)cellTapped:(FlexGrid *)sender panel:(GridPanel *)panel forRange:(GridCellRange *)tappedRange{  

    range = tappedRange;  
    return false;  
}  
-(bool)cellDoubleTapped:(FlexGrid *)sender panel:(GridPanel *)panel forRange:(GridCellRange *)tappedRange{  
    inEditMode = true;  
    range = tappedRange;  
    return false;  
}  

We can also provide some other functions that might require input outside of the FlexGrid. For example, we can add a full text filter (much the same as what’s implemented in FlexGrid101). We can Implement this as a UIAlertViewController which will display as a popover on our grid.


- (void) filterKeyPress {  
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Filter" message:@"Filter text in grid" preferredStyle:UIAlertControllerStyleAlert];  
    [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {  
        textField.placeholder = @"Enter text:";  

    }];  
    [alert addAction:[UIAlertAction actionWithTitle:@"Filter" style:UIAlertActionStyleDefault handler:^  
                      (UIAlertAction *filter){  

                          [self fullTextFilter:[alert.textFields objectAtIndex:0]];  

                          range = [[GridCellRange alloc] initWithRow:0 col:0 row2:0 col2:0];  
                          [grid selectCellRange:[[GridCellRange alloc] initWithRow:0 col:0 row2:0 col2:0] show:true];  
                          [alert dismissViewControllerAnimated:true completion:nil];  
                      }  

                      ]];  
    [self presentViewController:alert animated:YES completion:nil];  
}  

This gives as an alert where the user can enter string, confirm it, and filter his or her data. keyboardfilter

Summing things up

Adding key command shortcuts will greatly enhance the user experience for anyone with a keyboard, and even make a developer’s life easier by enabling keyboard text entry during simulator tests. They bring a finer level of control to your apps, and don’t require a huge amount of code to implement. In the future, we'll look at implementing some keyboard support directly into the FlexGrid control, but this will give you an excellent starting point for using the feature in the current control.