2010年9月17日 星期五

How to implement customized keyboard on iOS4

在 iPhone 上面開發應用程式時, 在使用輸入鍵盤時, 或多或少都會遇到客制化鍵盤的問題, 這邊筆者以
簡單的數字鍵盤來示範客制化的動作. 這部份我想網路上已經有不少 sample code , 但大部份基本上都是
以 SDK 3.x 的版本去實作, 以"特定寫法"來實作客制化在 iOS4 會有問題, 這部份稍候會提到兩版本的差異.


上述看到的例子是 UIKeyboardTypeNumberPad 搭配 "Done" 的圖示所組合而成的. 在開
始介紹如何實作之前, 先稍微提一下網路上查到的一些範例寫法. 因為 SDK 升版之
後在架構上有做了些修改, 所以導致行為上的不正確. 以下面這例子為例, 它可以正
常的在 iOS4 之前的版本運行, 但在 iOS4 上卻會有看不到上面 "Done" 圖示的問題.

- (void)loadView{
    ...
    textFieldContent.delegate = self;
    textFieldContent.placeholder = @"press me";
    textFieldContent.keyboardType = UIKeyboardTypeNumberPad;
    textFieldContent.returnKeyType = UIReturnKeyDone;
    [self.view addSubview:textFieldContent];
    [textFieldContent release];
    
    [[NSNotificationCenter defaultCenteraddObserver:self 
                                    selector:@selector(keyboardWillShow:) 
                                        name:UIKeyboardWillShowNotification 
                                      object:nil];
    
}

- (void)keyboardWillShowOnDelay:(NSNotification *)notification{
    UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
    doneButton.frame = CGRectMake(0, 163, 106, 53);
    doneButton.adjustsImageWhenHighlighted = NO;
    [doneButton setImage:[UIImage imageNamed:@"DoneUp.png"] forState:UIControlStateNormal];
    [doneButton setImage:[UIImage imageNamed:@"DoneDown.png"] forState:UIControlStateHighlighted];
    [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
    
    UIWindow* tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];
    UIView* keyboard;
    for(int i=0; i<[tempWindow.subviews count]; i++) {
        keyboard = [tempWindow.subviews objectAtIndex:i];
        if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)
            [keyboard addSubview:doneButton];
    }
}

上述這段代碼主要原理是透過跟 OS 註冊 keyboard 相關的 notification, 並在顯示
keyboard 時, 在 keyboard view 上添加所需要的特定 UIView, 簡單流程大致如下
1. 註冊 UIKeyboardWillShowNotification : 當 keyboard 要秀時, OS 就會呼叫
    keyboardWillShow
2.  當被 keyboardWillShow 叫用時, 搜尋屬於 keyboard 的 view
   if([[keyboard descriptionhasPrefix:@"<UIKeyboard"] == YES)
3. 當找到所需要的 view 時, 再將需要的 view 加入即可
   [keyboard addSubview:doneButton];
上面就是一個 customized keyboard 的簡單實作流程. 但是為什麼這段 code 會無法
在 iOS4 上正確執行呢? 問題點主要出在上述的第 2 個步驟.
在舊的 SDK 中, 當 UIKeyboardWillShowNotification 事件發生且叫用 keyboardWillShow 
時, 此時的 keyboard view 已經被添加到 windows 裡了, 但是在 iOS4 的世界中, 相同
情況發生時, keyboard view 卻會在下個 event loop 裡才會被添加到 windows 中, 也
就是因為如此, 所以上述
[[[UIApplication sharedApplicationwindowsobjectAtIndex:1];
會找不到 keyboard view. 除了這原因以外, 還有另一個重要的差異性, 第 2 步驟所比
對的 @"<UIKeyboard" 字串在 iOS4 中也被修正過, 它被藏在 @"<UIPeripheralHostView"
裡.
針對這兩點, 所以將只要將之修正即可正常的在 iOS4 上執行
1. keyboard view
   既然知道是 keyboard view 會在下個 event loop 才會被放到 windows 裡, 所以我們
   可以透過下面方式將 keyboardWillShow 延遲叫用
   [self performSelector:@selector(keyboardWillShow:) withObject:nil afterDelay:0];
2. 修正比對 @"<UIKeyboard" 的方式

   if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"]) 
       possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];

   if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"])  
   {
       foundKeyboard = possibleKeyboard;
       break;
   }                
經過上述兩個修正之後的 code 大概會如下 :
    [[NSNotificationCenter defaultCenter] addObserver:self 
                    selector:@selector(keyboardWillShowOnDelay:) 
                        name:UIKeyboardWillShowNotification 
                      object:nil];

- (void)keyboardWillShowOnDelay:(NSNotification *)notification
{
    [self performSelector:@selector(keyboardWillShow:) withObject:nil afterDelay:0]; 
}

- (void)keyboardWillShow:(NSNotification *)notification
{    
    UIView *foundKeyboard = nil;
    
    UIWindow *keyboardWindow = nil;
    for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) 
    {
        if (![[testWindow class] isEqual:[UIWindow class]]) 
        {
            keyboardWindow = testWindow;
            break;
        }
    }
    if (!keyboardWindow) return;
    
    for (UIView *possibleKeyboard in [keyboardWindow subviews]) 
    {            
        //iOS3
        if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"]) 
        {
            foundKeyboard = possibleKeyboard;
            break;
        }
        else
        {
            // iOS 4 sticks the UIKeyboard inside a UIPeripheralHostView.
            if ([[possibleKeyboard description] hasPrefix:@"<UIPeripheralHostView"]) 
            {
                possibleKeyboard = [[possibleKeyboard subviews] objectAtIndex:0];
            }                                                                                
            
            if ([[possibleKeyboard description] hasPrefix:@"<UIKeyboard"]) 
            {
                foundKeyboard = possibleKeyboard;
                break;
            }                
        }            
    }
    
    if (foundKeyboard) 
    {
        // create custom button
        UIButton *doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
        doneButton.frame = CGRectMake(0, 163, 106, 53);
        doneButton.adjustsImageWhenHighlighted = NO;
        [doneButton setImage:[UIImage imageNamed:@"DoneUp.png"] forState:UIControlStateNormal];
        [doneButton setImage:[UIImage imageNamed:@"DoneDown.png"] forState:UIControlStateHighlighted];
        [doneButton addTarget:self action:@selector(doneButton:) forControlEvents:UIControlEventTouchUpInside];
        
        [foundKeyboard addSubview:doneButton];
    }
}
這邊要注意到的是, 因為同一個 app 有可能會在新舊版本的 OS 上執行, 所以若只有
針對 iOS4 而做修改, 就有可能會造成舊 iPhone 無法正常值行, 有鑒於此, 上述這段
 code 針對比對字串做了點小技巧, 讓此 code 可以符合新舊版本.
完整的範例可以到 這裡 下載

沒有留言:

張貼留言