この投稿はiOS Advent Calendar 2014の13日目の記事です。

こんにちは。13日目担当のharanicleです。
先にあやまっておきますが、小ネタです。。

iOS8のカスタムキーボード

iOS8になってAppExtensionという仕組みで、カスタムキーボードを作成できるようになりました。
僕も1つカスタムキーボードアプリをリリースしています。
良かったら買ってね!

特殊文字キーボード

カスタムキーボードは便利だけど、自分のアプリだけで使えるカスタムキーボードがほしい!
カスタムキーボードもいいけど、標準キーボードが一番使いやすい!
そうだ、標準キーボードを拡張すればいいんだ!という奇特な人のためにこんなものを作ってみました。

customkeyboard

解説

UITextEffectsWindow

UITextEffectsWindow

iOSアプリのキーボードは、UITextEffectsWindowという、
AppDelegateが持っているUIWindow(簡単のためメインウインドウと
呼びます)とは、別のUIWindowで表示されます。
なので、キーボードの上に何かを表示するためには、
メインウインドウとは別のUIWindow(簡単のためにサブウインドウと呼びます)
を作成し、UITextEffectsWindowの上にサブウインドウを表示する必要があります。

サブウインドウの開閉

UIWindowを開く

UIWindowを開く処理のコードです。
sushiWindowを作成し、windowLevelをUIWindowLevelNormalより少し大きい値をセットして、UITextEffectsWindowよりも上にsushiWindowを表示しています。
objc_setAssociatedObjectでsushiWindowのオーナーを設定しています。
sushiWindowは閉じるときにnilを代入して開放したいのでOptional型にしています。
また、windowにお寿司の絵文字を入力できるボタンを設置しています。
(ゴリゴリのハードコーディングですみません…)

func openSushiWindow() {
    sushiWindow = UIWindow(frame: CGRect(x: 230, y: 621, width: 42, height:42))
    sushiWindow?.windowLevel = UIWindowLevelNormal + 5
    sushiWindow?.makeKeyAndVisible()
    sushiWindow?.addSubview(sushiButton as UIView)
    sushiWindow?.backgroundColor = UIColor.orangeColor()
    
    objc_setAssociatedObject(UIApplication.sharedApplication(), &sushiWindowKey, sushiWindow, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
}

UIWindowを閉じる

objc_setAssociatedObjectでnilをセットして、サブウインドウの所有権を開放しています。
sushiWindowにnilを代入してオブジェクトを開放しています。

func closeShushiWindow() {
    // サブウインドウを破棄
    objc_setAssociatedObject(UIApplication.sharedApplication(), &sushiWindowKey, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
    sushiWindow = nil
    
    // メインウインドウをキーウインドウにする。
    UIApplication.sharedApplication().windows.first?.makeKeyAndVisible()
}

キーボード表示、非表示のイベント

NSNotificationCenterで取得できます。

Observerの設定

キーボード表示、非表示のイベントを受け取るObserverの設定と除去です。
今回は、viewDidAppearとviewDidDisappearで実行しました。
キーボード表示時にサブウインドウを開き、非表示時にサブウインドウを閉じるようにしました。

override func viewDidAppear(animated: Bool) {
    // キーボード表示、非表示の通知を受け取る。
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardDidHide:", name: UIKeyboardDidHideNotification, object: nil)
}

override func viewDidDisappear(animated: Bool) {
    // 通知を受け取らないようにする。
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

/**
キーボードが表示された時の処理
:param: notificaiton 通知
*/
func keyboardDidShow(notificaiton:NSNotification) {
    println(__FUNCTION__)
    openSushiWindow()
}

/**
キーボードが非表示になった時の処理
:param: notificaiton 通知
*/
func keyboardDidHide(notificaiton:NSNotification) {
    println(__FUNCTION__)
    closeShushiWindow()
}

サンプルコード

以下に今回のコードを置いておきます。
haranicle/CustomKeyboardWithoutExtension

まとめ

この方法だとAppExtensionを使っていないので、iOS7以下でも使用できますね。
きっと、LINEのスタンプを選択するUIはこの方法でやってるんだと思います。

参考