Search code examples
keyboardios8ios-app-extension

iOS 8 Custom Keyboard: Changing the Height


I have tried to create a custom keyboard in iOS 8 that replaces the stock one. I really searched and could not find out if it is possible to create a keyboard with more height than the stock iOS keyboard. I replaced UIInputView but could never manage to change the height available to me.


Solution

  • This is my code on Xcode 6.0 GM. Both orientations are supported.

    Update: Thanks to @SoftDesigner, we can eliminate the constraint conflict warning now.

    Warning: XIB and storyboard are not tested. It's been reported by some folks that this does NOT work with XIB.

    KeyboardViewController.h

    #import <UIKit/UIKit.h>
    
    @interface KeyboardViewController : UIInputViewController
    
    @property (nonatomic) CGFloat portraitHeight;
    @property (nonatomic) CGFloat landscapeHeight;
    @property (nonatomic) BOOL isLandscape;
    @property (nonatomic) NSLayoutConstraint *heightConstraint;
    @property (nonatomic) UIButton *nextKeyboardButton;
    
    @end
    

    KeyboardViewController.m

    #import "KeyboardViewController.h"
    
    @interface KeyboardViewController ()
    @end
    
    @implementation KeyboardViewController
    
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
        if (self) {
            // Perform custom initialization work here
            self.portraitHeight = 256;
            self.landscapeHeight = 203;
        }
        return self;
    }
    
    - (void)updateViewConstraints {
        [super updateViewConstraints];
        // Add custom view sizing constraints here
        if (self.view.frame.size.width == 0 || self.view.frame.size.height == 0)
            return;
    
        [self.inputView removeConstraint:self.heightConstraint];
        CGSize screenSize = [[UIScreen mainScreen] bounds].size;
        CGFloat screenH = screenSize.height;
        CGFloat screenW = screenSize.width;
        BOOL isLandscape =  !(self.view.frame.size.width ==
                          (screenW*(screenW<screenH))+(screenH*(screenW>screenH)));
        NSLog(isLandscape ? @"Screen: Landscape" : @"Screen: Potriaint");
        self.isLandscape = isLandscape;
        if (isLandscape) {
            self.heightConstraint.constant = self.landscapeHeight;
            [self.inputView addConstraint:self.heightConstraint];
        } else {
            self.heightConstraint.constant = self.portraitHeight;
            [self.inputView addConstraint:self.heightConstraint];
        }
    }
    
    - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // Perform custom UI setup here
        self.nextKeyboardButton = [UIButton buttonWithType:UIButtonTypeSystem];
    
        [self.nextKeyboardButton setTitle:NSLocalizedString(@"Next Keyboard", @"Title for 'Next Keyboard' button") forState:UIControlStateNormal];
        [self.nextKeyboardButton sizeToFit];
        self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = NO;
    
        [self.nextKeyboardButton addTarget:self action:@selector(advanceToNextInputMode) forControlEvents:UIControlEventTouchUpInside];
    
        [self.view addSubview:self.nextKeyboardButton];
    
        NSLayoutConstraint *nextKeyboardButtonLeftSideConstraint = [NSLayoutConstraint constraintWithItem:self.nextKeyboardButton attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0];
        NSLayoutConstraint *nextKeyboardButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.nextKeyboardButton attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0];
        [self.view addConstraints:@[nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint]];
    
    
        self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.inputView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:self.portraitHeight];
    
        self.heightConstraint.priority = UILayoutPriorityRequired - 1; // This will eliminate the constraint conflict warning.
    
    }
    
    - (void)textWillChange:(id<UITextInput>)textInput {
        // The app is about to change the document's contents. Perform any preparation here.
    }
    
    - (void)textDidChange:(id<UITextInput>)textInput {
    }
    
    @end
    

    Swift 1.0 version:

    class KeyboardViewController: UIInputViewController {
    
        @IBOutlet var nextKeyboardButton: UIButton!
    
        let portraitHeight:CGFloat = 256.0
        let landscapeHeight:CGFloat = 203.0
        var heightConstraint: NSLayoutConstraint?
        override func updateViewConstraints() {
            super.updateViewConstraints()
            // Add custom view sizing constraints here
            if (self.view.frame.size.width == 0 || self.view.frame.size.height == 0) {
                return
            }
            inputView.removeConstraint(heightConstraint!)
            let screenSize = UIScreen.mainScreen().bounds.size
            let screenH = screenSize.height;
            let screenW = screenSize.width;
            let isLandscape =  !(self.view.frame.size.width == screenW * ((screenW < screenH) ? 1 : 0) + screenH * ((screenW > screenH) ? 1 : 0))
            NSLog(isLandscape ? "Screen: Landscape" : "Screen: Potriaint");
            if (isLandscape) {
                heightConstraint!.constant = landscapeHeight;
                inputView.addConstraint(heightConstraint!)
            } else {
                heightConstraint!.constant = self.portraitHeight;
                inputView.addConstraint(heightConstraint!)
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Perform custom UI setup here
            self.nextKeyboardButton = UIButton.buttonWithType(.System) as UIButton
    
            self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), forState: .Normal)
            self.nextKeyboardButton.sizeToFit()
        self.nextKeyboardButton.setTranslatesAutoresizingMaskIntoConstraints(false)
    
            self.nextKeyboardButton.addTarget(self, action: "advanceToNextInputMode", forControlEvents: .TouchUpInside)
    
            self.view.addSubview(self.nextKeyboardButton)
    
            var nextKeyboardButtonLeftSideConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1.0, constant: 0.0)
            var nextKeyboardButtonBottomConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
            self.view.addConstraints([nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint])
    
            heightConstraint = NSLayoutConstraint(item: self.inputView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: portraitHeight)
            heightConstraint!.priority = 999.0
        }
    
        override func textWillChange(textInput: UITextInput) {
            // The app is about to change the document's contents. Perform any preparation here.
        }
    
        override func textDidChange(textInput: UITextInput) {
            // The app has just changed the document's contents, the document context has been updated.
    
            var textColor: UIColor
            var proxy = self.textDocumentProxy as UITextDocumentProxy
            if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
                textColor = UIColor.whiteColor()
            } else {
                textColor = UIColor.blackColor()
            }
            self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
        }
    }