天天看點

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調整到合适的位置,第二種取固定值的方式雖然也能達到效果但是不太穩妥,如果使用第三方輸入法或者系統鍵盤固定高度改變,都會出現問題。