鍵盤開發(fā)其實遇到了很多坑郁油,之前未記錄下來本股,想起來哪些記錄哪些吧...
1. 鍵盤高度調(diào)整不生效
鍵盤高度的調(diào)整必須通過約束控制,但是在viewDidload
中設(shè)置約束桐腌,并不能生效拄显,最終高度還是系統(tǒng)設(shè)置的高度(可以看到是系統(tǒng)后添加了高度約束并生效)
思路就是在系統(tǒng)設(shè)置約束后再添加自定義的高度約束,前后嘗試了很多方案
- 方案1:在
viewDIdAppear
中設(shè)置高度約束案站,但其實時機有些晚 - 方案2:重寫
UIInputView
子類的-setFrame
方法躬审,當(dāng)判斷frame高度>0并且≠想設(shè)置的高度時,重新設(shè)置高度約束蟆盐,這個方案用了一段時間承边,沒發(fā)現(xiàn)什么問題 - 方案3:重寫
- (void)updateViewConstraints
方法,進(jìn)行約束判斷修改
- (void)updateViewConstraints{
[self updateKeyBoardHeight];
[super updateViewConstraints];
}
- (void)updateKeyBoardHeight{
CGFloat newHeight = 500; // 這里高度根據(jù)業(yè)務(wù)計算
if (self.heightConstraint.constant == newHeight) {
return;
}
if (self.heightConstraint) {
self.heightConstraint.constant = newHeight;
return;
}
self.heightConstraint = [NSLayoutConstraint constraintWithItem: self.view
attribute: NSLayoutAttributeHeight
relatedBy: NSLayoutRelationEqual
toItem: nil
attribute: NSLayoutAttributeNotAnAttribute
multiplier: 0.0
constant: newHeight];
self.heightConstraint.priority = UILayoutPriorityDefaultHigh;
[self.view addConstraint:self.heightConstraint];
}
2. 鍵盤邊緣按鈕點擊- touchBegin...
調(diào)用慢
由于我們鍵盤按鈕點擊是通過重寫 touchs系列方法 分發(fā)事件的石挂,發(fā)現(xiàn)靠近屏幕邊緣的按鈕博助,點擊高亮效果較難觸發(fā),當(dāng)時看了搜狗輸入法也是有同樣的問題痹愚,就沒有研究原因富岳,歸結(jié)系統(tǒng)bug了....
很久之后發(fā)現(xiàn)百度輸入法是正常的,便又研究了一番拯腮,發(fā)現(xiàn)點擊邊緣按鈕時窖式,- touchBegin
和 - touchEnd
的時間間隔極短,其它區(qū)域按鈕就是正常的动壤,猜測是視圖層級中存在Gesture手勢 延遲了- touchBegin
或者 未延遲 - touchEnd
(因為手勢有兩個屬性 delaysTouchesEnded
和delaysTouchesBegan
)
通過遍歷視圖發(fā)現(xiàn)
[9744:673157] <_UIHostedWindow: 0x105f096b0; frame = (0 0; 390 280); gestureRecognizers = <NSArray: 0x281e79380>; layer = <UIWindowLayer: 0x281043920>>
[9744:673157] <_UISystemGestureGateGestureRecognizer: 0x105f0b240; state = Possible; delaysTouchesBegan = YES; view = <_UIHostedWindow 0x105f096b0>>
果然_UIHostedWindow上存在一個手勢的 delaysTouchesBegan = YES;
// 方案就是:
// 修改這個手勢的 delaysTouchesBegan = NO;
// 我把代碼放在了這里
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_async(dispatch_get_main_queue(), ^{
// 這是為了解決 鍵盤邊緣點擊時 touchBegin和touchEnd的間隔極端 導(dǎo)致點擊高亮效果無法顯示
// 這個修改會導(dǎo)致一個控制臺警告 也沒發(fā)現(xiàn)啥影響 暫時就不管了
// [Warning] Trying to set delaysTouchesBegan to NO on a system gate gesture recognizer - this is unsupported and will have undesired side effects
for (UIGestureRecognizer *ges in self.view.window.gestureRecognizers) {
if (ges.delaysTouchesBegan) {
ges.delaysTouchesBegan = NO;
// ges.delaysTouchesEnded = YES;
}
}
});
}
3. 鍵盤內(nèi)存限制66M
這個其實沒啥說的萝喘,代碼上及時釋放就行了,特別是圖片加載上尤其要注意
4. 鍵盤實例 UIInputViewController實例獲取 (巨坑)
不知道其他家是怎么獲取的狼电,我們早期在 Controller的 - init中 用一個全局指針記錄下當(dāng)前鍵盤實例蜒灰, 在其他業(yè)務(wù)中都是通過這個指針獲取當(dāng)前實例進(jìn)行業(yè)務(wù)處理,這也埋下了一個很深的隱患肩碟,這個bug足足有一年多未解決..
在某種情況下强窖,系統(tǒng)會創(chuàng)建多個UIInputViewController實例,而當(dāng)前顯示的卻可能是不是最后一次創(chuàng)建的實例削祈,就導(dǎo)致代碼中所有獲取鍵盤實例的地方都錯誤的獲取了最后一次創(chuàng)建的鍵盤實例翅溺,從而導(dǎo)致無法輸入上屏/UI更新失敗等一系列問題。
在調(diào)查中髓抑,我們早期走錯了方向咙崎,一直沒想過會同時出現(xiàn)多個鍵盤實例,再加上很難復(fù)現(xiàn)吨拍,竟然一直沒發(fā)現(xiàn)是我們代碼上取錯了實例對象...
這個解決方案其實很簡單:不要通過全局指針方式獲取最后一次init的鍵盤實例褪猛,在創(chuàng)建子業(yè)務(wù)實例時,在各自的基類中弱引用當(dāng)前鍵盤實例即可羹饰,或者一層一層向上弱引用也可以伊滋。
5. 鍵盤實例 UIInputViewController 生命周期函數(shù)調(diào)用多次
多次實驗發(fā)現(xiàn)UIInputViewController實例碳却,并不是每次彈出鍵盤都重新實例化,系統(tǒng)會在一些場景復(fù)用之前創(chuàng)建的鍵盤實例
笑旺,即 init viewDidLoad 執(zhí)行一次昼浦,但 willAppear/DidAppear/willDisappear/DidDisappear 會調(diào)用多次,需要注意防止一些寫在生命周期方法中的初始化代碼執(zhí)行多次筒主。(-viewWillDisappear 這個方法特殊关噪,正常收起鍵盤,貌似就會調(diào)用兩次乌妙, 尚不知道原因...)
6. 鍵盤View中多點觸控失效(這個問題也比較坑使兔,宿主APP的部分設(shè)置會影響到鍵盤)
我們鍵盤中的按鈕點擊事件是通過-touchBegin...系列方法處理的,一個按鈕按下狀態(tài)冠胯,是允許點擊第二個按鈕的火诸,這套方案也一直未出現(xiàn)過問題锦针。
一天用戶反饋了視頻荠察,演示了第一個按鈕不抬起,第二個按鈕無法按下的情況奈搜;查了很長時間始終不能復(fù)現(xiàn)悉盆,代碼上也不能發(fā)現(xiàn)問題,后來忽然想到有沒可能是宿主app導(dǎo)致的馋吗,按照視頻中宿主調(diào)試看看焕盟,果然在“斗魚app”里面彈出鍵盤[UIView appearance].exclusiveTouch = YES
,全局圖層的點擊事件被開啟了互斥宏粤,在其它地方彈出鍵盤就是好的脚翘,這意味著宿主app內(nèi)部對于UIAppearance
做的一些設(shè)置,會同步生效在鍵盤上
解決方案就是
// 在鍵盤主Controller的聲明周期方法里
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// 坑爹 有的宿主程序(斗魚)用下列代碼開啟了互斥 竟然鍵盤里也被開啟了 會導(dǎo)致多點觸控失效....
// 這里為了確保 關(guān)閉touch事件互斥 放在鍵盤出現(xiàn)時執(zhí)行
if ([UIView appearance].exclusiveTouch == YES) {
[UIView appearance].exclusiveTouch = NO;
}
// 可以記錄一個標(biāo)志位 如果宿主開啟了互斥 這里臨時關(guān)閉 在viewDidDisappear里在開啟 確保不影響宿主APP
// 對于宿主app 其實一般開啟UIButton或UIControl的事件互斥就夠了
// 如: [UIButton appearance].exclusiveTouch = YES;
}
7. 鍵盤inputView的寬度和UIScreen的寬度不一致
導(dǎo)致了鍵盤布局寬度超出屏幕绍哎,如下圖
這個主要是宿主App導(dǎo)致的来农,前段時間發(fā)現(xiàn)在"IT橙子"這個app里面彈出鍵盤時,鍵盤的寬度撐出了屏幕崇堰,通過對比百度/搜狗輸入法沃于,要么也是撐出屏幕,要么鍵盤彈出時也是撐出屏幕海诲,彈出后抖動了一下才恢復(fù)正常繁莹,如下圖
解決方案
在鍵盤VC的
viewDidAppear
方法里,重新獲取一次inputView的寬度特幔,和已經(jīng)計算好的布局對比咨演,如果寬度發(fā)生變化,就重新計算布局并刷新蚯斯,唯一的弊端就是會抖一下(百度輸入法也會)薄风,反正是極少情況才會出現(xiàn)零院。沒有深究宿主APP干了啥會導(dǎo)致這個問題??????
8. 獲取鍵盤彈出時的App包名(hostBundleID)
iOS16之前比較簡單
// 在鍵盤入口controller中(即UIInputViewController的子類)
[self.parentViewController valueForKey:@"_hostBundleID"]
iOS16上述方法獲取到的是nil,也沒查到相關(guān)解決方案村刨,看到xxx輸入法需要判斷包名的業(yè)務(wù)依然正常告抄,就逆向分析了下其代碼實現(xiàn)????,如下
#import <dlfcn.h>
+ (NSString *)hostBundleIDWithInputVC:(UIInputViewController *)vc {
NSString *bundleID = nil;
@try {
bundleID = [vc.parentViewController valueForKey:@"_hostBundleID"];
} @catch (NSException *exception) {
NSLog(@"hostBundleID Get Failure 1 %@", exception);
} @finally {
}
if(XMUtils.validString(bundleID) && ![bundleID isEqual:@"<null>"]) {
return bundleID;
}
// 云控禁止xpc取法
// xpc取法 逆向自xx輸入法 主要解決iOS16上獲取不到的問題
@try {
id hostPid = [vc.parentViewController valueForKey:@"_hostPID"];
if (hostPid) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
id serverIns = [NSClassFromString(@"PKService") performSelector:NSSelectorFromString(@"defaultService")];
id lities = [serverIns performSelector:NSSelectorFromString(@"personalities")];
id infos = [lities objectForKey:[NSBundle mainBundle].bundleIdentifier];
id info = [infos objectForKey:hostPid];
id con = [info performSelector:NSSelectorFromString(@"connection")];
id xpcCon = [con performSelector:NSSelectorFromString(@"_xpcConnection")];
#pragma clang diagnostic pop
const char * (* cfunc)(id) = dlsym(RTLD_DEFAULT, "xpc_connection_copy_bundle_id");
if (cfunc != nil && xpcCon != nil) {
const char *res = cfunc(xpcCon);
bundleID = [NSString stringWithUTF8String:res];
return bundleID;
}
}
} @catch (NSException *exception) {
NSLog(@"hostBundleID Get Failure 2 %@", exception);
} @finally {
}
return nil;
}