天天看点

iOS学习笔记——输入法切换时动态调整UITextField的位置

最近做项目时做了一个发送短信的界面,有点类似QQ聊天。在输入文本时textField(或者UITextView,这里以UITextField为例)会随着键盘的弹起而改变位置,避免输入时textField被键盘挡住。我的做法是直接根据keyboard的固定高度来改变textField所在的view的frame,把view上移。在模拟器上经过各种测试发现没什么问题,但是用真机测试时发现当改变输入法时,keyboard的高度也会跟着改变,这个时候textField所在的view有可能依然会被遮住。尤其是iOS7的真机上,因为引入了中文九宫格输入法,导致keyboard的frame有好几种:

// 英文键盘高度                   216.0f   

// 中文键盘高度                     252.0f   

// 中文九宫格键盘未输入高度         184.f   

// 中文九宫格输入键盘高度                          251.5f 

那么怎样根据键盘高度的变化来改变textField的位置以达到textField不被遮住的目的呢?

网上搜索之后好像没找到类似的方法,于是自己动手解决,经过试验大体总结出两种方法,都是用notification实现的,但是在实现方式上有些不同。

第一种方法:使用UIKeyboardDidChangeFrameNotification

#import "TestViewController.h"

#define IOS_VERSION ([[UIDevice currentDevice] systemVersion].floatValue)

@interface TestViewController ()<UITextFieldDelegate>{
    CGFloat deltaY;
    float duration;    // 动画持续时间
    CGFloat originalY; // TextField原来的纵坐标
}

@property (retain, nonatomic) UITextField *testField;
@property (assign, nonatomic) NSInteger keyboardPresentFlag; // 键盘弹起的标志,弹出=1,收起=0

@end

@implementation TestViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    _testField = [[UITextField alloc] initWithFrame:CGRectMake(60, 300, 200, 30)];
    _testField.delegate = self;
    _testField.borderStyle = UITextBorderStyleRoundedRect;
    [self.view addSubview:_testField];
    // 获取textField初始位置的Y轴坐标,这里就是30
    originalY = self.testField.frame.origin.y;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardFrameDidChange:) name:UIKeyboardDidChangeFrameNotification object:nil];
}

- (void)keyboardWillShow:(NSNotification *)notification {
    CGSize keyboardSize = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    // 获得弹出keyboard的动画时间,也可以手动赋值,如0.25f
    duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
    
    // 得到keyboard在当前controller的view中的Y轴坐标
    CGFloat keyboardOriginY = self.view.frame.size.height - keyboardSize.height;
    
    // textField下边到view顶点的距离减去keyboard的Y轴坐标就是textField要移动的距离,这里是刚好让textField完全显示出来,也可以再在deltaY的基础上再加上一定距离,如20f、30f等
    deltaY = self.testField.frame.origin.y + self.testField.frame.size.height - keyboardOriginY;
    
    // 当deltaY大于0时说明textField会被键盘遮住,需要上移
    if (deltaY > 0) {
        // 以动画的方式改变textField的frame
        [UIView animateWithDuration:duration delay:0.f options:UIViewAnimationOptionCurveEaseIn animations:^{
            self.testField.frame = CGRectOffset(self.testField.frame, 0, -deltaY);
        } completion:nil];
    }
}

- (void)keyboardWillHide:(NSNotification *)notification {
    // 键盘收起时将textField的位置还原
    [UIView animateWithDuration:duration delay:0.f options:UIViewAnimationOptionCurveEaseIn animations:^{
        CGRect frame = self.testField.frame;
        frame.origin.y = originalY;
        self.testField.frame = frame;
    } completion:nil];
}

/**
 * 键盘frame变化时执行的通知方法
 * @note 键盘弹出,收起,改变输入法时这个方法都会执行
 */
- (void)keyboardFrameDidChange:(NSNotification *)notification {
    CGSize keyboardSize = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    CGFloat keyboardOriginY = self.view.frame.size.height - keyboardSize.height;
    deltaY = self.testField.frame.origin.y + self.testField.frame.size.height - keyboardOriginY;
    
    // 键盘在弹出的情况下如果frame有变化就改变textField的位置
    if (self.keyboardPresentFlag == 1) {
        [UIView animateWithDuration:duration delay:0.f options:UIViewAnimationOptionCurveEaseIn animations:^{
            self.testField.frame = CGRectOffset(self.testField.frame, 0, -deltaY);
        } completion:nil];
    }
}

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    self.keyboardPresentFlag ++;
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    self.keyboardPresentFlag --;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view endEditing:YES];
}

@end
           

第二种方法,根据UITextInputMode来实现,改变输入法时键盘高度会发生变化,根据这个固定高度来实现,这种方法实际上有一定缺陷,如果是iOS8里第三方输入法,键盘高度有可能可以手动调整,这样就会出问题。我只用了iOS6的iPod touch 4测试了一下,以上面说到过的英文键盘高度和中文键盘高度为例。

#import "TestViewController.h"

#define IOS7 ([[UIDevice currentDevice] systemVersion].floatValue >= 7.0)
#define KEYBOARD_HEIGHT_EN 216
#define KEYBOARD_HEIGHT_ZH 252

@interface TestViewController ()<UITextFieldDelegate>{
    CGFloat deltaY;
    CGFloat originalY; // TextField原来的纵坐标
}

@property (retain, nonatomic) UITextField *testField;
@property (assign, nonatomic) NSInteger keyboardPresentFlag; // 键盘弹起的标志,弹出=1,收起=0
@property (copy, nonatomic) NSString *currentInputMode;      // 当前输入法

@end

@implementation TestViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    _testField = [[UITextField alloc] initWithFrame:CGRectMake(60, 400, 200, 30)];
    _testField.delegate = self;
    _testField.borderStyle = UITextBorderStyleRoundedRect;
    [self.view addSubview:_testField];
    // 获取textField初始位置的Y轴坐标,这里就是30
    originalY = self.testField.frame.origin.y;
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputModeDidChange:) name:UITextInputCurrentInputModeDidChangeNotification object:nil];
}

- (void)inputModeDidChange:(NSNotification *)notification {
    if (IOS7) {
        self.currentInputMode = self.testField.textInputMode.primaryLanguage;
    } else {
        self.currentInputMode = [[UITextInputMode currentInputMode] primaryLanguage];
    }
    NSLog(@"input mode: %@", self.currentInputMode);
    
    [self getDeltaYByInputMode:self.currentInputMode];
    
    [UIView animateWithDuration:0.25f animations:^{
        self.testField.frame = CGRectOffset(self.testField.frame, 0, -deltaY);
    }];
    if (self.keyboardPresentFlag == 0) {
        self.keyboardPresentFlag ++;
    }
}

#pragma mark - UITextFieldDelegate

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    [self getDeltaYByInputMode:self.currentInputMode];
    
    if (deltaY > 0 && self.keyboardPresentFlag == 0) {
        [UIView animateWithDuration:0.25f animations:^{
            self.testField.frame = CGRectOffset(self.testField.frame, 0, -deltaY);
        }];
        self.keyboardPresentFlag ++;
    }
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    // 结束输入时将textField移回原处
    if (self.keyboardPresentFlag == 1) {
        [UIView animateWithDuration:0.25f animations:^{
            CGRect frame = self.testField.frame;
            frame.origin.y = originalY;
            self.testField.frame = frame;
        }];
        self.keyboardPresentFlag --;
    }
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField resignFirstResponder];
    return YES;
}

/**
 * 根据当前输入法改变textField的位移
 */
- (void)getDeltaYByInputMode:(NSString *)inputMode {
    if ([inputMode isEqualToString:@"zh-Hans"]) {
        deltaY = self.testField.frame.origin.y + self.testField.frame.size.height - (self.view.frame.size.height - KEYBOARD_HEIGHT_ZH);
    } else if ([inputMode isEqualToString:@"en-US"]) {
        deltaY = self.testField.frame.origin.y + self.testField.frame.size.height - (self.view.frame.size.height - KEYBOARD_HEIGHT_EN);
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [self.view endEditing:YES];
}

@end
           

个人推荐第一种方法,这样不管键盘的frame怎样变化总能够把textField调整到合适的位置,第二种取固定值的方式虽然也能达到效果但是不太稳妥,如果使用第三方输入法或者系统键盘固定高度改变,都会出现问题。