
為什么要做這個效果
在聊天app,例如微信中,你會注意到一個效果,就是在你點擊輸入框時輸入框會跟隨鍵盤一起向上彈出,當(dāng)你點擊其他地方時,輸入框又會跟隨鍵盤一起向下收回,二者完全無縫連接,那么這是怎么實現(xiàn)的呢,也許你會說直接在鍵盤彈出的時候把輸入框也向上移動不就行了?但是我使用這種方法的時候,發(fā)現(xiàn)效果十分不理想,會有明顯的滯后現(xiàn)象,原因有以下幾點:
1.鍵盤彈出動畫并不是勻速,鍵盤和輸入框的時間曲線不完全一致,運動不同步
2.各種鍵盤的高度不一樣(比如搜狗輸入法就比系統(tǒng)自帶鍵盤要高)
3.無法確定鍵盤動畫的時間,會導(dǎo)致延遲
解決方案
使用本地通知,對鍵盤的狀態(tài)(彈出、收回)進行監(jiān)控,當(dāng)鍵盤狀態(tài)發(fā)生改變時,在相應(yīng)的方法中對輸入框的位置進行操作。
這里應(yīng)用了兩種在ios編程中很重要的思想:
Key-value coding(KVC) 和key-value observing(KVO)
1.使用NSNotificationCenter.defaultCenter().addObserver()添加對UIKeyboardWillShowNotification和UIKeyboardWillHideNotification鍵的監(jiān)控,當(dāng)這些值發(fā)生改變時發(fā)送通知
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyBoardWillShow:", name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector:"keyBoardWillHide:", name:UIKeyboardWillHideNotification, object: nil)
2.實現(xiàn)兩個監(jiān)控方法
實現(xiàn)鍵盤彈出的方法:
func keyBoardWillShow(note:NSNotification)
{
//1
let userInfo = note.userInfo as! NSDictionary
//2
var keyBoardBounds = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
//3
var keyBoardBoundsRect = self.view.convertRect(keyBoardBounds, toView:nil)
//4
var keyBaoardViewFrame = keyBaordView.frame
var deltaY = keyBoardBounds.size.height
//5
let animations:(() -> Void) = {
self.keyBaordView.transform = CGAffineTransformMakeTranslation(0,-deltaY)
if duration > 0 {
let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16))
UIView.animateWithDuration(duration, delay: 0, options:options, animations: animations, completion: nil)
}else{
animations()
}
}
代碼分析
//1
let userInfo = note.userInfo as! NSDictionary
將通知的用戶信息取出,轉(zhuǎn)化為字典類型,里面所存的就是我們所需的信息:鍵盤動畫的時長、時間曲線;鍵盤的位置、高度信息。有了這些信息我們就可以do some magic了~
//2
通過對應(yīng)的鍵UIKeyboardFrameEndUserInfoKey,取出鍵盤位置信息
通過UIKeyboardAnimationDurationUserInfoKey,取出動畫時長信息
//3
var keyBoardBoundsRect = self.view.convertRect(keyBoardBounds, toView:nil)
由于取出的位置信息是絕對的,所以要將其轉(zhuǎn)換為對應(yīng)于當(dāng)前view的位置,否則位置信息會出錯!
//4
var keyBaoardViewFrame = keyBaordView.frame
var deltaY = keyBoardBounds.size.height
保存下輸入框的位置信息和y坐標(biāo)需要變換的量以便后面調(diào)用
//5
let animations:(() -> Void) = {
self.keyBaordView.transform = CGAffineTransformMakeTranslation(0,-deltaY)
if duration > 0 {
let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16))
UIView.animateWithDuration(duration, delay: 0, options:options, animations: animations, completion: nil)
}else{
animations()
}
}
首先使用仿射變換CGAffineTransformMakeTranslation,使輸入框的高度減少deltaY也就是跟隨鍵盤的位置向上移動;
此處難點在這里
let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16))
這里是將時間曲線信息(一個64為的無符號整型)轉(zhuǎn)換為UIViewAnimationOptions類型,要通過左移16來完成類型轉(zhuǎn)換。
這個方法是在一個比較著名的解決bug的網(wǎng)站stackoverflow里找到的。
自我感覺這是比較坑的地方,它居然沒有用來進行類型轉(zhuǎn)換的方法,竟然還得要位!運!算!不過相信今后這個坑會被apple填上吧。。
然后呢就是把這些東西全部裝進UIView的動畫函數(shù)中,執(zhí)行動畫。
UIView.animateWithDuration(duration, delay: 0, options:options, animations: animations, completion: nil)
這樣鍵盤彈出的方法就完全實現(xiàn)了!
接下來就是收回鍵盤的部分了:
這部分呢就比較簡單了,收回鍵盤時只需要動畫時長duration和時間曲線信息options所以只要留下他們就行了,然后再將輸入框的位置還原即可,這里有一個很巧妙的辦法
self.keyBaordView.transform = CGAffineTransformIdentity
這樣就可以還原所有變換~
下面是該方法的實現(xiàn):
func keyBoardWillHide(note:NSNotification)
{
let userInfo = note.userInfo as! NSDictionary
let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
let animations:(() -> Void) = {
self.keyBaordView.transform = CGAffineTransformIdentity
}
if duration > 0 {
let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16))
UIView.animateWithDuration(duration, delay: 0, options:options, animations: animations, completion: nil)
}else{
animations()
}
}
實際上這個方法不會運行,因為并沒有判斷是否應(yīng)該收回鍵盤,我的解決方法是當(dāng)手指點擊輸入框之上的任何地方就會收回鍵盤,這個在我的完整demo會看到。
demo的效果:
