0

My app has a passcode view, conceptually similar to the iOS unlock screen. It's a UIViewController that I present within a new UIWindow. Works fine. I'm adding the capability to type the passcode using a hardware keyboard. The keyCommands method is not called, thus key presses not recognized, until the user taps anywhere on the screen at least once. It's a full-screen UIWindow/UIViewController so presumably it's a tap within the UIWindow/UIViewController. Once that tap occurs, keyCommands will be called as expected, and all works perfectly. I don't want to require the user to tap their screen before typing their passcode.

Any idea what's going on here, specifically why the user needs to tap the screen (and how to avoid that requirement)?

I have verified the UIViewController is the firstResponder by including a repeating NSTimer call to verify as such.

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self becomeFirstResponder];
    [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) { //This is just debugging code!
        [self confirmFirstResponder];
    }];
}

-(BOOL)canBecomeFirstResponder {
    return YES;
}

-(void)confirmFirstResponder { //Caveman debugging at its finest
    if ([self isFirstResponder]) {
        NSLog(@"I'm first responder!"); //This is always logged repeatedly
    } else {
        NSLog(@"I'm NOT THE FIRST RESPONDER!!!!"); //This is never logged
    }
}

-(NSArray<UIKeyCommand *> *)keyCommands {
    NSLog(@"keyCommands fired"); //This is not fired until user taps the screen, then presses a key on the hardware keyboard
    NSArray<UIKeyCommand *> *commands = @[
        [UIKeyCommand commandWithTitle:@"1" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"1" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"2" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"2" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"3" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"3" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"4" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"4" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"5" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"5" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"6" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"6" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"7" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"7" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"8" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"8" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"9" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"9" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"0" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"0" modifierFlags:0 propertyList:nil],
        [UIKeyCommand commandWithTitle:@"Delete" image:nil action:@selector(buttonPressedWithKeyCommand:) input:@"\b" modifierFlags:0 propertyList:nil]
    ];
    return commands;
}

Here's the code to create the UIWindow:

-(void)displayNewWindowWithViewController:(UIViewController *)vc {
    UINavigationController *nav=[[UINavigationController alloc] initWithRootViewController:vc]; //vc is the UIViewController containing the code above
    nav.navigationBarHidden=YES; //I have no recollection why I'm wrapping the vc in a UINavigationController...
    self.modalWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    self.modalWindow.backgroundColor = [UIColor whiteColor];
    self.modalWindow.clipsToBounds = NO;
    self.modalWindow.rootViewController = nav;
    self.modalWindow.windowLevel=99;
    [self makeKeyAndVisible:self.modalWindow];
}

-(void)makeKeyAndVisible:(UIWindow *)window {
    window.backgroundColor = [UIColor clearColor];
    window.frame=CGRectMake(0, [UIScreen mainScreen].bounds.size.height, window.frame.size.width, window.frame.size.height);
    [window makeKeyAndVisible];
    window.frame=CGRectMake(0, 0, window.frame.size.width, window.frame.size.height);
}

Other than the hardware keyboard code I'm asking about, I wrote everything here nearly seven years ago. So I don't remember the specific logic for...anything. I do know I used a UIWindow because this is a security view, which absolutely must be over all other views, including some that the app may be adding while this is visible. Ideal or not, it's been working perfectly. If a substantive rearchitecture is needed to make the hardware keyboard work here, the hardware keyboard capability will be dropped.

Mitch Cohen
  • 1,551
  • 3
  • 15
  • 29
  • For what it's worth: The numeric "buttons" are UIButtons. They are triggered on first press as expected. The user's tap to enable keyboard input can be anywhere on the view, both in a UIButton or in any "inert" area of the view. – Mitch Cohen Mar 01 '21 at 17:50
  • You should not be misusing a secondary UIWindow in this way. But if you do create a window, making it key is up to you. It sounds to me like the tap is going that missing step. However, you didn't show us _any_ of the really important code, namely, how you create this window and make it key. Personally I would just say: don't do that, use a presented view controller and all will be well. – matt Mar 01 '21 at 22:45
  • Thanks @matt. I added the UIWindow code to the question. It is made keyAndVisible. – Mitch Cohen Mar 01 '21 at 23:43
  • Well, I repeat my recommendation: don't do that. Just use a presented view controller. You can size and place the view however you like, so there is no advantage in using a UIWindow. – matt Mar 02 '21 at 00:45

1 Answers1

0

The new UIWindow wasn't quite the "key window" in terms of capturing keyboard events, even with the makeKeyandVisible instruction. I verified this by temporarily adding the same code to a UIViewController on the app's main UIWindow. It received the keyboard events until I tapped the screen (the new UIWindow).

I changed this:

[window makeKeyAndVisible]

to this:

window.hidden = NO;
dispatch_async(dispatch_get_main_queue(), ^{
    [window makeKeyWindow];
});

and suddenly the keyboard was fully captured by my new UIWindow.

I'm not quite sure what was going on. The original [window makeKeyAndVisible] was definitely running on the main thread (verified with [NSThread isMainThread]). But throwing it into another run loop did the trick. Go figure.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Mitch Cohen
  • 1,551
  • 3
  • 15
  • 29