可調整高度的客製化鍵盤

蘇醒宇
Dcard Tech Blog
Published in
7 min readApr 20, 2022

Dcard 在 2021 年中上線了貼圖的功能,不知道大家是不是都用過了呢?本篇將分享我們如何實現客製化鍵盤與系統鍵盤互相切換的功能。

貼圖功能

本文將提及如何「切換鍵盤」以及「動態改變鍵盤高度」,分為「切換鍵盤」和「鍵盤模板」兩個部分,不會提到貼圖鍵盤內容的實作方式。

本文切換鍵盤與高度調整範例

切換鍵盤

在切換鍵盤的功能上,我們有幾個設計想要實現,以符合系統原生的行為:

  • 如果畫面上沒有鍵盤,則鍵盤應該從螢幕底部滑入畫面內
  • 如果畫面上已有鍵盤,則應立即切換至新的鍵盤

首先,若一個 UITextView 要使用客製化的鍵盤,系統有提供 UITextView.inputView 的參數來設定客製化的 view 作為鍵盤使用, 相反的,當UITextView.inputView 被設定為 nil 時,UITextView 則會使用系統鍵盤。我們先建立一個 function 來描述這件事。

這時我們會發現當 UITextView 正在編輯時( UITextView.isFirstResponder == true),修改 UITextView.inputView 並沒有任何效果。這時候,當我們更新 .inputView 後,我們需要額外執行 UITextView.reloadInputViews() 讓 UITextView 更新目前的 .inputView

下面範例的最後,我們把 keyboard manager 的實體設定到 keyboard-template view 身上(line: 15)。這邊是為了讓 keyboard-template view 可以取得上次鍵盤高度的資訊,詳細會在下個章節「鍵盤模板」說明。

接下來,為了在切換鍵盤時,新的鍵盤可以和前一次的系統鍵盤一樣高,我們會需要記下最後鍵盤的高度,這邊透過接收 UIResponder.keyboardWillChangeFrameNotificationUIResponder.keyboardDidChangeFrameNotification來獲取目前鍵盤的高度並記錄下來。同時,為了方便重複利用,我們將以上的功能封裝為 KeyboardManager。

你或許會想說既然我們都接收 keyboardWillChangeFrame 的事件了,為什麼還要接收 keyboardDidChangeFrame 的事件呢?這是因為在 iPad 上,iOS 14 的盤在 splitting 模式調整位置時,會先發送 keyboardWillChangeFrame 事件, 其keyboardFrameEndUserInfoKey 的值為 CGSize.zero ,等到型態切換完畢時,才會發送keyboardDidChangeFrame,這時keyboardFrameEndUserInfoKey 的值才會是正確的。

// 開始拖曳 splitting keyboard
----- will change frame = (0.0, 0.0, 0.0, 0.0)
----- will change frame = (0.0, 0.0, 0.0, 0.0)
// 結束拖曳
----- did change frame = (0.0, 197.0, 1112.0, 261.0)
----- did change frame = (0.0, 142.0, 1112.0, 316.0)

鍵盤模板

接下來我們要製作一個鍵盤的模板,這個鍵盤模板將會封裝高度設定及調整的功能。一般來說,我們會在 input view 被呈現前,設定 UIView.frame.size.height 來設定鍵盤的高度,但這個作法無法在鍵盤呈現後動態的改變高度。

為了要在鍵盤呈現後才改變高度,會需要在 input view 身上加上一個高度的 NSLayoutConstraint, 且 input view 的 .intrinsicContentSize 也需要回傳一樣的高度,這樣才能順利動作。

這邊我們建立一個 KeyboardTemplateView 來實作高度相關的功能,透過 .preferredHeight 來改變鍵盤高度,讓之後客製化的鍵盤可以直接繼承這個類別來實作。

接下來我們建立一個 HeightLayout 來描述鍵盤的高度,之後繼承 KeyboardTemplateView 實作鍵盤的類別,可以透過修改這個參數去設定鍵盤高度。高度類型分為 systemcustom兩種。system 會需要給定「最小」、「最大」及「預設」高度,在鍵盤呈當下畫面上沒有系統的鍵盤可以參照高度時,就會使用「預設」高度。若是從系統鍵盤切換過來的話,則使用最後系統鍵盤的高度,但不超過「最大」。

system模式下「最小」高度同時會被套用在使用外接鍵盤的情況,在這種情況下,會沒有鍵盤的高度,因此需要一個最小高度來避免客製化鍵盤消失。

custom 則是直接指定鍵盤的高度,為了讓有固定高度的鍵盤可以不受系統鍵盤高度的影響。下面我們透過 enum 來建立 HeightLayout,在 KeyboardTemplateView.updatePreferredHeight() 中,實作上述的需求。

在上面的範例第 33 行處,在計算前一次鍵盤高度時,會扣掉當前的 frame.minY 原因是上方可能還有 accessory view 或是 assistant view (iPad 裝置),但系統 keyboardWillChangeFrame 通知中給的會是包含上述兩個元件的整體高度,因此扣除 frame.minY 才能得到鍵盤內容真正的高度。

Keyboard View Hierarchy

整合起來

把上面的實作整合起來後,我們建立一個 ViewController 來展示實際上的應用方式。這邊我們有個繼承 KeyboardTemplateView 的 DemoKeyboardView,在點擊 switch-keyboard button 時,會切換至 demo keyboard。

DemoKeyboardView 是繼承自 KeyboardTemplateView,透過修改 .preferredHeight 來改變高度,這邊就讓大家自己試試看囉。

以上就是 Dcard 在實作客製化鍵盤上的分享,有任何問題都可以在留言與我們交流喔!

另外,最近我們正在招募 iOS 的新夥伴加入,iOS 團隊專注於開發社群、廣告、電商、跨國(香港及日本)等產品,想暸解更多的朋友們,可以一起來看看之前介紹 iOS 團隊介紹的文章,也歡迎你們來聊聊!

Senior iOS Developer — https://dcard.link/DCCCrm
iOS Developer, E-commerce — https://dcard.link/CFZT7F
Senior iOS Developer, Advertising — https://dcard.link/OXzgGU

--

--