iOS 8自定义键盘:更改高度

我试图在iOS 8中创build一个自定义键盘来replace原来的一个。 我真的search,无法找出是否有可能创build一个比股票iOS键盘更高的键盘。 我replace了UIInputView,但无法设法改变可用的高度。

这是我在Xcode 6.0 GM上的代码。 两个方向都被支持。

更新:感谢@SoftDesigner,我们现在可以消除constraint conflict警告。

警告 :XIB和故事板未经testing。 据一些人士报道,这不适用于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版本:

 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) } } 

最近,苹果已经更新了他们的应用程序扩展编程指南,以改变自定义键盘扩展的高度:

 CGFloat _expandedHeight = 500; NSLayoutConstraint *_heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant: _expandedHeight]; [self.view addConstraint: _heightConstraint]; 

这是我发现导致高度得到正确更新的最小的解决scheme。 似乎有两个关键组成部分:

  • translatesAutoresizingMaskIntoConstraints设置为false的视图需要添加到视图层次结构中。
  • 高度约束需要添加不早于viewWillAppear

我仍然看到一个Unable to simultaneously satisfy constraints日志中的Unable to simultaneously satisfy constraints错误,但它似乎仍然工作正常。 我还看到一个跳跃,高度初始设置为默认值,然后跳转到设置值。 我还没有想出解决这些问题的办法。

 import UIKit class KeyboardViewController: UIInputViewController { var heightConstraint: NSLayoutConstraint! override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.inputView.addConstraint(self.heightConstraint) } override func viewDidLoad() { super.viewDidLoad() let dummyView = UILabel(frame:CGRectZero) dummyView.setTranslatesAutoresizingMaskIntoConstraints(false) self.view.addSubview(dummyView); let height : CGFloat = 400 self.heightConstraint = NSLayoutConstraint( item:self.inputView, attribute:.Height, relatedBy:.Equal, toItem:nil, attribute:.NotAnAttribute, multiplier:0.0, constant:height) } } 

接受的答案是不适用于iOS 9.我结合它的一些和其他一些build议,以及应用程序扩展编程指南中的苹果的代码。

这个解决scheme效果很好,因为它不会延迟修改高度,直到viewDidAppear ,并根据旋转,您可以根据屏幕大小更改高度。 validation了这个工程在iOS 8和9。

一些重要的注意事项:
inputView至less有一个元素需要使用Auto Layout
viewWillAppear才能激活高度限制
〜高度约束的priority需要降低以避免不可约束的约束
updateViewConstraints是设置所需高度的好地方

提示:
〜在模拟器上进行testing时,我发现它非常脆弱,performance出色。 如果这样做给你,重置模拟器并再次运行。 或者,您可能只能禁用键盘并重新添加。

注意:
〜这目前不在iOS 10 beta版本中。 当它出现时它会适当地改变高度,但是如果你旋转设备,高度不会改变。 这是因为updateViewConstraints不会在循环中触发。 请针对iOS 10提交错误报告。要解决该问题,您可以触发viewDidLayoutSubviewsconstant更改。

 var nextKeyboardButton: UIButton! var heightConstraint: NSLayoutConstraint? override func viewDidLoad() { super.viewDidLoad() self.nextKeyboardButton = UIButton(type: .System) self.nextKeyboardButton.setTitle(NSLocalizedString("Next Keyboard", comment: "Title for 'Next Keyboard' button"), forState: .Normal) self.nextKeyboardButton.sizeToFit() self.nextKeyboardButton.translatesAutoresizingMaskIntoConstraints = false self.nextKeyboardButton.addTarget(self, action: "advanceToNextInputMode", forControlEvents: .TouchUpInside) self.view.addSubview(self.nextKeyboardButton) let nextKeyboardButtonLeftSideConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Left, relatedBy: .Equal, toItem: self.view, attribute: .Left, multiplier: 1, constant: 0) let nextKeyboardButtonBottomConstraint = NSLayoutConstraint(item: self.nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: 0) NSLayoutConstraint.activateConstraints([nextKeyboardButtonLeftSideConstraint, nextKeyboardButtonBottomConstraint]) } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) self.heightConstraint = NSLayoutConstraint(item:self.inputView!, attribute:.Height, relatedBy:.Equal, toItem:nil, attribute:.NotAnAttribute, multiplier:0, constant:0) self.heightConstraint!.priority = 999 self.heightConstraint!.active = true } override func updateViewConstraints() { super.updateViewConstraints() guard self.heightConstraint != nil && self.view.frame.size.width != 0 && self.view.frame.size.height != 0 else { return } let portraitHeight: CGFloat = 400 let landscapeHeight: CGFloat = 200 let screenSize = UIScreen.mainScreen().bounds.size let newHeight = screenSize.width > screenSize.height ? landscapeHeight : portraitHeight if (self.heightConstraint!.constant != newHeight) { self.heightConstraint!.constant = newHeight } } 

其他答案没有考虑到冲突约束和设备旋转。 这个答案避免了像“无法同时满足约束”的错误以及由此产生的问题。 它部分依赖于在未来版本的iOS中可能会改变的行为,但似乎是解决iOS 8上这个问题的唯一方法。

在你的UIInputViewController子类中,添加这些方法:

 - (void)updateViewConstraints { [super updateViewConstraints]; // Update height when appearing [self updateViewHeightConstraintIfNeeded]; } - (void)viewWillLayoutSubviews { [super viewWillLayoutSubviews]; // Update height when rotating [self updateViewHeightConstraintIfNeeded]; } - (void)updateViewHeightConstraintIfNeeded { CGFloat preferedHeight = 216; // Portrait if ( [UIScreen mainScreen].bounds.size.width > [UIScreen mainScreen].bounds.size.height ) { // Landscape preferedHeight = 162; } NSLayoutConstraint *constraint = [self findViewHeightConstraint]; if ( preferedHeight != constraint.constant ) { if ( constraint ) { constraint.constant = preferedHeight; } else { // This is not run on current versions of iOS, but we add it to // make sure the constraint exits constraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:preferedHeight]; [self.view.superview addConstraint:constraint]; } } } - (NSLayoutConstraint*)findViewHeightConstraint { NSArray *constraints = self.view.superview.constraints; for ( NSLayoutConstraint *constraint in constraints ) { if ( constraint.firstItem == self.view && constraint.firstAttribute == NSLayoutAttributeHeight ) return constraint; } return nil; } 

把它放在ViewDidAppear中:

 NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem: self.view attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 0.0 constant: 300]; [self.view addConstraint: heightConstraint]; 

适用于iOS 8.1

我有一个自定义键盘从iOS 8到iOS 10的大小相似的问题。我相信正确的解决scheme是让input视图提供一个适当的intrinsicContentSize和更改视图的高度(并无效!)的值。 示例代码:

 class CustomInputView: UIInputView { var intrinsicHeight: CGFloat = 200 { didSet { self.invalidateIntrinsicContentSize() } } init() { super.init(frame: CGRect(), inputViewStyle: .keyboard) self.translatesAutoresizingMaskIntoConstraints = false } required init?(coder: NSCoder) { super.init(coder: coder) self.translatesAutoresizingMaskIntoConstraints = false } override var intrinsicContentSize: CGSize { return CGSize(width: 0, height: self.intrinsicHeight) } } class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() textView.becomeFirstResponder() let inputView = CustomInputView() // To make the view's size more clear. inputView.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0.5, alpha: 1) textView.inputView = inputView // To demonstrate a change to the view's intrinsic height. DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(2)) { inputView.intrinsicHeight = 400 } } } 
 - (void)updateViewConstraints { [super updateViewConstraints]; // Add custom view sizing constraints here CGFloat _expandedHeight = 500; NSLayoutConstraint *_heightConstraint = [NSLayoutConstraint constraintWithItem: self.view attribute: NSLayoutAttributeHeight relatedBy: NSLayoutRelationEqual toItem: nil attribute: NSLayoutAttributeNotAnAttribute multiplier: 0.0 constant: _expandedHeight]; [self.view addConstraint: _heightConstraint]; } -(void)viewDidAppear:(BOOL)animated { [self updateViewConstraints]; } 

它为我工作

我创造了这个作品,对我来说很好。 添加prepareHeightConstraint()heightConstraint,并在你的updateViewConstraints和viewWillAppear中调用prepareHeightConstraint()

  private var heightConstraint: NSLayoutConstraint! /** Prepare the height Constraint when create or change orientation keyboard */ private func prepareHeightConstraint() { guard self.heightConstraint != nil else { let dummyView = UILabel(frame:CGRectZero) dummyView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(dummyView) self.heightConstraint = NSLayoutConstraint( item:self.view, attribute:.Height, relatedBy:.Equal, toItem:nil, attribute:.NotAnAttribute, multiplier:0.0, constant: /* Here your height */) // /* Here your height */ Here is when your create your keyboard self.heightConstraint.priority = 750 self.view.addConstraint(self.heightConstraint!) return } // Update when change orientation etc.. self.heightConstraint.constant = /* Here your height */ } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) // When keyboard is create self.prepareHeightConstraint() } override func updateViewConstraints() { super.updateViewConstraints() guard let viewKeyboard = self.inputView where viewKeyboard.frame.size.width != 0 && viewKeyboard.frame.size.width != 0 { return } //Update change orientation, update just the constant self.prepareHeightConstraint() } 

为了更方便的animation改变方向我添加这个:

 - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator { CGFloat width = [UIScreen mainScreen].bounds.size.width; self.view.window.frame = CGRectMake(0, 0, width, heigth); } 

我也一直在做一个键盘和高度问题挣扎。 我尝试了所有提到的解决scheme,也是应用程序扩展编程指南的解决scheme,但无法修复。 我的键盘具有非常复杂的视图层次。 挣扎后,我发现一个完全为我工作的解决scheme。 这是一种黑客,但我已经testing了所有的场景,甚至与设备的旋转,这是完美的。 我认为这会帮助别人,所以我把我的代码放在这里..

 // Keep this code inside the UIInputViewController @implementation KeyBoardViewController @property (strong, nonatomic) NSLayoutConstraint *heightConstraint; // This method will first get the height constraint created by (Run time system or OS) then deactivate it and add our own custom height constraint. (void)addHeightConstraint { for (NSLayoutConstraint* ct in self.view.superview.constraints) { if (ct.firstAttribute == NSLayoutAttributeHeight) { [NSLayoutConstraint deactivateConstraints:@[ct]]; } } if (!_heightConstraint) { _heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant: 300]; [_heightConstraint setPriority:UILayoutPriorityDefaultHigh]; [self.view addConstraint:_heightConstraint]; }else { _heightConstraint.constant = 300; } if (_heightConstraint && !_heightConstraint.isActive) { [NSLayoutConstraint activateConstraints:@[_heightConstraint]]; } [self.view layoutIfNeeded]; } (void)viewWillLayoutSubviews { [self addHeightConstraint]; } 

这是不可能的。 从文档

另外,不可能在顶行上方显示关键graphics,就像系统键盘在iPhone上按下顶行中的按键时所做的一样。

所以,如果可能的话,我们可以很容易地在上面画一些东西。

编辑:

看来苹果公司修正了这个事情。 请参阅接受的答案

如果接受的答案是不工作的任何一个然后使用下面的way.all代码将是一样的只是改变代码里面updateViewConstraints 参考

 - (void)updateViewConstraints { [super updateViewConstraints]; 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"); if (isLandscape) { self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.inputView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant: self.landscapeHeight]; [self.inputView addConstraint:self.heightConstraint]; } else { self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.inputView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant: self.portraitHeight]; [self.inputView addConstraint:self.heightConstraint]; } } 

最后我明白了,将这个代码块添加到你的UIInputViewController子类中:

 override func viewDidAppear(animated: Bool) { let desiredHeight:CGFloat = 300.0 // or anything you want let heightConstraint = NSLayoutConstraint(item: view, attribute:NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: desiredHeight) view.addConstraint(heightConstraint) } 

它将工作完美.. iOS 8.3

这是我为iOS9Storyboard所做的。

我已经使用@ skyline75489的(非常感谢)答案并对其进行了修改。

 @property (nonatomic) CGFloat portraitHeight; @property (nonatomic) CGFloat landscapeHeight; @property (nonatomic) BOOL isLandscape; @property (nonatomic) NSLayoutConstraint *heightConstraint; @property (nonatomic) BOOL viewWillAppearExecuted; - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { // Perform custom initialization work here self.portraitHeight = 256; self.landscapeHeight = 203; } return self; } - (void)updateViewConstraints { [super updateViewConstraints]; if (_viewWillAppearExecuted) [self adjustHeight]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.view addConstraint:self.heightConstraint]; _viewWillAppearExecuted = YES; } #pragma mark - Setters/Getters - (NSLayoutConstraint *)heightConstraint { if (!_heightConstraint) { _heightConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0.0 constant:self.portraitHeight]; _heightConstraint.priority = UILayoutPriorityRequired - 1; } return _heightConstraint; } #pragma mark - Methods - (void)adjustHeight { if (self.view.frame.size.width == 0 || self.view.frame.size.height == 0) return; [self.view 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))); self.isLandscape = isLandscape; if (isLandscape) { self.heightConstraint.constant = self.landscapeHeight; [self.view addConstraint:self.heightConstraint]; } else { self.heightConstraint.constant = self.portraitHeight; [self.view addConstraint:self.heightConstraint]; } }