Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop UIKeyCommand repeated actions

If a key command is registered, it's action might be called many times if the user holds down the key too long. This can create very weird effects, like ⌘N could repeatedly open a new view many times. Is there any easy way to stop this behavior without resorting to something like a boolean "already triggered" flag?

Here's how I register two different key commands:

#pragma mark - KeyCommands

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (NSArray<UIKeyCommand *>*)keyCommands {
    return @[
             [UIKeyCommand keyCommandWithInput:@"O" modifierFlags:UIKeyModifierCommand action:@selector(keyboardShowOtherView:) discoverabilityTitle:@"Show Other View"],
             [UIKeyCommand keyCommandWithInput:@"S" modifierFlags:UIKeyModifierCommand action:@selector(keyboardPlaySound:) discoverabilityTitle:@"Play Sound"],
             ];
}

- (void)keyboardShowOtherView:(UIKeyCommand *)sender {
    NSLog(@"keyboardShowOtherView");
    [self performSegueWithIdentifier:@"showOtherView" sender:nil];
}

- (void)keyboardPlaySound:(UIKeyCommand *)sender {
    NSLog(@"keyboardPlaySound");
    [self playSound:sender];
}

#pragma mark - Actions

- (IBAction)playSound:(id)sender {
    AudioServicesPlaySystemSound(1006); // Not allowed in the AppStore
}

A sample project can be downloaded here: TestKeyCommands.zip

like image 511
Brent Avatar asked Jan 18 '17 23:01

Brent


2 Answers

In general, you don't need to deal with this, since the new view would usually become the firstReponder and that would stop the repeating. For the playSound case, the user would realize what is happening and take her finger off of the key.

That said, there are real cases where specific keys should never repeat. It would be nice if Apple provided a public API for that. As far as I can tell, they do not.

Given the '//Not allowed in the AppStore' comment in your code, it seems like you're OK using a private API. In that case, you could disable repeating for a keyCommand with:

UIKeyCommand *keyCommand =  [UIKeyCommand ...];
[keyCommand setValue:@(NO) forKey:@"_repeatable"];
like image 191
Rich Waters Avatar answered Oct 16 '22 16:10

Rich Waters


I reworked @Ely's answer a bit:

extension UIKeyCommand {
    var nonRepeating: UIKeyCommand {
        let repeatableConstant = "repeatable"
        if self.responds(to: Selector(repeatableConstant)) {
            self.setValue(false, forKey: repeatableConstant)
        }
        return self
    }
}

Now you can have to write less code. If for example just override var keyCommands: [UIKeyCommand]? by returning a static list it can be used like this:

override var keyCommands: [UIKeyCommand]? {
    return [
        UIKeyCommand(...),
        UIKeyCommand(...),
        UIKeyCommand(...),

        UIKeyCommand(...).nonRepeating,
        UIKeyCommand(...).nonRepeating,
        UIKeyCommand(...).nonRepeating,
    ]
}

This makes the first three command repeating (like increasing font size) and the last three ones non repeating (like sending an email).

Works with Swift 4, iOS 11.

like image 6
Hans Avatar answered Oct 16 '22 16:10

Hans