[iOS 8] UILexicon を使ってカスタムキーボードに用語集を表示する

用語集の参照

iOS を触ったことがある人はよくご存知かと思いますが、iOS の文字入力には予測変換機能が付いています。入力済みの文字を元に目的の単語を予測し、キーボードの上部に表示してくれます。当たり前な機能ですけど改めて考えると非常に便利ですよね。

lexicon01

さて、iOS 8 から App Extension を利用してカスタムキーボードが作成できるようになりました。自由にデザインした独自の入力方式のキーボードを作れるようになったわけですが、このようなカスタムキーボードは用語集を組み合わせることができます。

利用するのは UILexicon というクラスです。ということで本稿では UILexicon を使ってカスタムキーボードと用語集を連係させる方法を紹介します。

なおカスタムキーボードのプロジェクト作成についてはこちらで解説していますので、カスタムキーボードのプロジェクトを作成の上ご覧ください。

用語集を参照する

用語集を参照するのは非常に簡単です。まずカスタムキーボードのプロジェクトを作成し UIInputViewController を継承している View Controller 内の文字入力している箇所などに、次のコードを追加します。

func buttonDidTouch(sender: UIButton) {
    if let input = textDocumentProxy as? UITextDocumentProxy {
        // 適当な文字入力
        input.insertText("A")
        // 文字入力ついでに用語集を取得
        requestSupplementaryLexiconWithCompletion({
            lexicon in
            for item in lexicon.entries {
                // 検索結果
                println("documentText: \(item.documentText)")
                // 検索結果とマッチしたユーザー入力値
                println("userInput: \(item.userInput)")
            }
        })
    }
}

UILexicon を取得するには UIInputViewController の requestSupplementaryLexiconWithCompletion: メソッドを呼びます。このメソッドは用語集による補足を要求します。引数には (UILexicon!) -> Void 型の関数オブジェクトを渡しますが、ここではクロージャを渡しています。

UILexicon には entries というプロパティがあり、このプロパティは UILexiconEntry オブジェクトの配列を保持しています(読み取り専用)。

UILexiconEntry のデータは次の通りです。

  • Address Book のデータベース内にある名前
  • キーボードショーカット (Settings > General > Keyboard > Shortcuts) に登録した文字列
  • 一般的な用語

UILexiconEntry には documentTextuserInput というプロパティを持っています。documentText は取得結果である値で、userInput はユーザーが入力したテキストのどの部分がマッチしたかを示す値です。

これを View などに設定して、キーボード上に表示すれば完成です。

lexicon02

…っと、カスタム辞書と Address Book のデータしか入ってこないですね。。一般的な用語は何故か取得できない状態のようです。

なお、今回実装のソースコードはこちらです。

import UIKit

class KeyboardViewController: UIInputViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var nextKeyboardButton: UIButton!
    @IBOutlet var tableView: UITableView!
    
    private var lexicon: UILexicon!

    override func viewDidLoad() {
        super.viewDidLoad()
    
        // ... 割愛 ...
        
        let button: UIButton = UIButton.buttonWithType(.System) as UIButton
        button.setTitle("A", forState: .Normal)
        button.addTarget(self, action: "buttonDidTouch:", forControlEvents: .TouchUpInside)
        button.center = CGPointMake(0, view.frame.size.height / 2)
        button.sizeToFit()
        view.addSubview(button)
        
        tableView = UITableView(frame: CGRectMake(50, 0, 300, 180))
        tableView.dataSource = self
        tableView.delegate = self
        view.addSubview(tableView)
    }
    
    // MARK: - Action
    
    func buttonDidTouch(sender: UIButton) {
        if let input = textDocumentProxy as? UITextDocumentProxy {
            // 適当な文字入力
            input.insertText("A")
            // 文字入力ついでに用語集を取得
            requestSupplementaryLexiconWithCompletion({
                lexicon in
                // UILexicon から取得したデータを表示
                self.lexicon = lexicon
                self.tableView.reloadData()
                for item in lexicon.entries {
                    // 検索結果
                    println("documentText: \(item.documentText)")
                    // 検索結果とマッチしたユーザー入力値
                    println("userInput: \(item.userInput)")
                }
            })
        }
    }
    
    // MARK: - UITableViewDataSource
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")
        if let entry: UILexiconEntry = lexicon.entries[indexPath.row] as? UILexiconEntry {
            cell.textLabel?.text =  entry.documentText
        }
        return cell
    }
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return lexicon != nil ? lexicon.entries.count : 0
    }
    
    // MARK: - UITableViewDelegate
    
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
        // タップしたアイテムの文字列を出力
        if let entry: UILexiconEntry = lexicon.entries[indexPath.row] as? UILexiconEntry {
            if let input = textDocumentProxy as? UITextDocumentProxy {
                input.insertText(entry.documentText)
            }
        }
    }
}

まとめ

デフォルトのキーボード同様、予測変換やユーザー辞書が参照できるのはとても便利ですね。これが使えるのと使えないのでは使いやすさに大きな違いがあると思います。

正直なところ、デフォルトのキーボードで出てくる予測変換の View が提供してもらいたいところですが、取得したデータを元に自力で作っていくしかないようです。

参考