0

I noticed that when I'm on the phone and the green call bar appears at the top of the screen, my custom keyboard won't appear (it's as if it gets skipped). So I debugged it and discovered that it's a constraint error.

Here is the error in the debugger:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The layout constraints still need update after sending -updateViewConstraints to <KeyboardViewController: 0x16dfbe30>.
KeyboardViewController or one of its superclasses may have overridden -updateViewConstraints without calling super or sending -updateConstraints to the view. Or, something may have dirtied layout constraints in the middle of updating them.  Both are programming errors.'
*** First throw call stack:
(0x2ab43c1f 0x382eec8b 0x2ab43b65 0x2e6300ef 0x2b7cb5b9 0x2e630203 0x2e0ef4e9 0x2b7cb5b9 0x2e0ef2c3 0x2e630487 0x2e29ba8b 0x2e0004d7 0x2da28a0d 0x2da243e5 0x2da2426d 0x2da23c51 0x2da23a55 0x2dff8965 0x2ab0a3b5 0x2ab07a73 0x2ab07e7b 0x2aa56211 0x2aa56023 0x31e4f0a9 0x2e0621d1 0x389cf9fb 0x389d1021 0x2b916635 0x33956065 0x33955d3b 0x33956099 0x37c7a4fd 0x3886eaaf)
libc++abi.dylib: terminating with uncaught exception of type NSException

It's important to note that I never get any errors/warnings in either orientation or orientation change when there isn't a call bar. I am using the following to adjust my keyboard's height from 216 to 270:

- (id)initWithCoder:(NSCoder *)aDecoder {

    if (self = [super initWithCoder:aDecoder]) {

        //Prepare our keyboard's values
        self.portraitHeight = 270;
        self.landscapeHeight = 190;
    }

    return self;
}

- (void)updateViewConstraints {

    //Super
    [super updateViewConstraints];

    //Check if self.view exists
    if (WIDTH(self.view) == 0 || HEIGHT(self.view) == 0) {return;}

    //Remove any previous constraints
    [self.inputView removeConstraint:self.heightConstraint];

    //Define landscape mode
    self.isLandscape = !(self.view.frame.size.width == ([[UIScreen mainScreen] bounds].size.width * ([[UIScreen mainScreen] bounds].size.width < [[UIScreen mainScreen] bounds].size.height)) + ([[UIScreen mainScreen] bounds].size.height *([[UIScreen mainScreen] bounds].size.width > [[UIScreen mainScreen] bounds].size.height)));

    //Update view height depending on orientation
    if (self.isLandscape) {
        //Readjust our keyboard's height
        self.heightConstraint.constant = self.landscapeHeight;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    } else {
        //Re-adjust our keyboard's height
        self.heightConstraint.constant = self.portraitHeight;
        self.heightConstraint.priority = 990;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    }
}

& here's my viewWillAppear:

- (void)viewWillAppear:(BOOL)animated {

    //Update our keyboard's height when view appears
    self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                         attribute:NSLayoutAttributeHeight
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:nil
                                                         attribute:NSLayoutAttributeNotAnAttribute
                                                        multiplier:0.0
                                                          constant:self.portraitHeight];
    self.heightConstraint.priority = 990;
    [self.view addConstraint:self.heightConstraint];
}

** Note: WIDTH() and HEIGHT() are macros.

I am using auto layout in my xib. I'm not sure where to begin addressing this.

UPDATE

Thanks for helping debug this @Remus! I'm now trying to detect when the device is being rotated within my extension on iOS 8, but my updateConstraints method is still not being called.

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];
    [self.view setNeedsLayout];
    [self.view updateConstraintsIfNeeded];
    //I've also tried [self.view updateConstraints]; instead
}

- (void)updateConstraints {

    NSLog(@"Check Width");
    //Check if self.view exists
    if (WIDTH(self.view) == 0 || HEIGHT(self.view) == 0) {return;}

    NSLog(@"Remove Old Constraints");
    //Remove any previous constraints
    [self.inputView removeConstraint:self.heightConstraint];

    NSLog(@"Define Landscape");
    //Define landscape mode
    self.isLandscape = !(self.view.frame.size.width == ([[UIScreen mainScreen] bounds].size.width * ([[UIScreen mainScreen] bounds].size.width < [[UIScreen mainScreen] bounds].size.height)) + ([[UIScreen mainScreen] bounds].size.height *([[UIScreen mainScreen] bounds].size.width > [[UIScreen mainScreen] bounds].size.height)));

    NSLog(@"Update view height depending on orientation.");
    //Update view height depending on orientation
    if (self.isLandscape) {
        //Readjust our keyboard's height
        self.heightConstraint.constant = self.landscapeHeight;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    } else {
        NSLog(@"Detected in portrait.");
        //Re-adjust our keyboard's height
        self.heightConstraint.constant = self.portraitHeight;
        self.heightConstraint.priority = 990;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    }
}

- (void)viewWillAppear:(BOOL)animated {

    NSLog(@"View Did Appear: Add Constraint.");
    [self.view updateConstraints];

    //Update our keyboard's height when view appears
    self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                         attribute:NSLayoutAttributeHeight
                                                         relatedBy:NSLayoutRelationEqual
                                                            toItem:nil
                                                         attribute:NSLayoutAttributeNotAnAttribute
                                                        multiplier:0.0
                                                          constant:self.portraitHeight];
    self.heightConstraint.priority = 990;
    [self.view addConstraint:self.heightConstraint];
}
KingPolygon
  • 4,753
  • 7
  • 43
  • 72
  • I think there's definitely something going on in the way you're overriding `updateViewConstraints`. Have you placed breakpoints inside that method to find out which line is actually triggering the constraint failure? – brandonscript Nov 10 '14 at 22:02
  • Oops! Good idea let try that out! – KingPolygon Nov 10 '14 at 22:29
  • Okay so this is weird. So apparently ```updateViewConstraints``` is being called 3 times. On the first pass it assumes the default height of 216, on the second pass it adjusts everything, and on the third pass it loads the last row (even though it allotted the space in the second space). The weird thing is when I set break points, everything worked correctly. So maybe it's something do to with timing? Maybe the constraints of the top bar interfere with the default constraints on my keyboard? – KingPolygon Nov 10 '14 at 22:59
  • Possibly.. What if you tried calling those functions in `updateConstraints` instead of `updateViewConstraints`? – brandonscript Nov 10 '14 at 23:01
  • Thanks Remus, but I can't seem to find a method call for ```updateConstraints```. hmm... – KingPolygon Nov 11 '14 at 00:17
  • You call that on a specific view - e.g. `[self.view updateConstraints]`. – brandonscript Nov 11 '14 at 00:19
  • Thanks Remus! So I put everything inside ```updateViewConstraints``` in ```updateViewConstraints instead``` and switched the super line accordingly. It seems to be working now, but when I switch to landscape it isn't updating appropriately – KingPolygon Nov 11 '14 at 01:09
  • In fact when I start in landscape it isn't working properly either and by properly I mean it isn't updating to its new height (190) – KingPolygon Nov 11 '14 at 01:15
  • For that I think you want to call `[view setNeedsLayout]` in the rotation event. Which on iOS 8 is [this](http://stackoverflow.com/questions/25238620/ios-8-rotation-methods-deprecation-backwards-compatibility) – brandonscript Nov 11 '14 at 01:36

1 Answers1

1

Let's try calling the functions inside of viewDidLayoutSubviews. This can also be called separately inside of any of the other constraint events (like updateViewConstraints, etc.).

- (void)viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];
    [self setKeyboardConstraints];
}

- (void)setKeyboardConstraints {

    NSLog(@"Check Width");
    //Check if self.view exists
    if (WIDTH(self.view) == 0 || HEIGHT(self.view) == 0) {return;}

    NSLog(@"Remove Old Constraints");
    //Remove any previous constraints
    [self.inputView removeConstraint:self.heightConstraint];

    NSLog(@"Define Landscape");
    //Define landscape mode
    self.isLandscape = !(self.view.frame.size.width == ([[UIScreen mainScreen] bounds].size.width * ([[UIScreen mainScreen] bounds].size.width < [[UIScreen mainScreen] bounds].size.height)) + ([[UIScreen mainScreen] bounds].size.height *([[UIScreen mainScreen] bounds].size.width > [[UIScreen mainScreen] bounds].size.height)));

    NSLog(@"Update view height depending on orientation.");
    //Update view height depending on orientation
    if (self.isLandscape) {
        //Readjust our keyboard's height
        self.heightConstraint.constant = self.landscapeHeight;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    } else {
        NSLog(@"Detected in portrait.");
        //Re-adjust our keyboard's height
        self.heightConstraint.constant = self.portraitHeight;
        self.heightConstraint.priority = 990;
        [self.inputView addConstraint:self.heightConstraint];
        self.view.frame = CGRectMake(0, 0, WIDTH(self.view), HEIGHT(self.view));
    }
}
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Thanks for your help Remus! So I've tried the following (updated my answer) but that method isn't being called at all (For now I'm just using NSLogs to see if it's being called) but I get nothing. – KingPolygon Nov 11 '14 at 02:17
  • Hm.. what about calling it in `viewDidLayoutSubviews` instead? – brandonscript Nov 11 '14 at 02:19
  • Okay so now it seems to be logging (3x when it first loads, 1x when its orientation switches. Thanks! But the updateConstraints method isn't being updated. Just to make sure do we call [super.self.view updateConstraints] or [self.view updateConstraints] inside the updateConstraints method? – KingPolygon Nov 11 '14 at 02:28
  • You don't want to call updateConstraints inside of itself - that's going to start an endless loop. You should just call `[self.view updateConstraints]` once inside a particular event or trigger (like keyboard appeared, viewWillAppear, or rotated). – brandonscript Nov 11 '14 at 02:33
  • Ohhh right I guess I expected a ```super```-like call like I used in ```updateViewConstraints```. I'm having one last issue. I can't seem to get ```updateConstraints``` to fire. I updated my answer above to give a clearer indication of what my methods look like. Thanks so much for your patience! – KingPolygon Nov 11 '14 at 02:51
  • Heh, you know, I think I'm going about this backwards. I think you were right the first time o_O. Perhaps the right thing to do is create a separate method (call it whatever you want), then call that method inside viewDidLayoutSubviews. – brandonscript Nov 11 '14 at 02:56
  • Ahh you're correct, by doing that the height is updated appropriately. I'm almost there! So if I do just that, the height is corrected, but the view is still off. But I'm sure I can figure it out soon thanks! – KingPolygon Nov 11 '14 at 06:23