iOS打造自定义输入法

2016/3/22 posted in  iOS

最近需要输入音标,一时间没有找到合适的App,于是就考虑自己写一个简单的音标输入法

要求:Swift 2,Xcode 7

iOS新手,代码略混乱



概述

APP Extension

App Extension允许用户在整个系统(iOS/OSX)使用App的一些功能,其中就包括自定义键盘.

自定义键盘

自定义键盘在苹果官方文档里已经介绍得比较详细了.
主要有以下几点注意:

  • 如果只是为你自己的App而打造的输入键盘,那就不要用自定义键盘,iOS提供了更好的选择: Custom Views for Data Input in Text Programming Guide for iOS.
  • 自定义键盘必须要能够切换到别的键盘
  • 自定义键盘在有时候是不能输入的(例如输入安全信息时,电话键盘...)
  • 自定义键盘是无法像系统键盘一样超出键盘范围范围
  • 一大堆限制,加密,麦克风等都不行

Let's Code

  1. 新建工程后新建一个Target,自然是选择Custom Keyboard
  2. 开始写界面,其实可以用Storyboard来写的,但是这里为了简单就直接写View&xib了 界面大概是这样: keyboardViewKVB collectionView里面显示48个基本IPA88音标,左下角的地球代表换键盘,右下角的DELETE代表删除 至于collectionView的Cell,我做的比较简单,就塞个Label进去 KeyCell
  3. 然后把对应的类写好
    KeyCell:

    //
    //  KeyCell.swift
    //  timelineDEMO
    //
    //  Created by 薛定谔弄死的猫 on 16/3/21.
    //  Copyright © 2016年 薛定谔弄死的猫. All rights reserved.
    //
    
    import UIKit
    
    @IBDesignable class KeyCell: UICollectionViewCell {
        var view :UIView!
    
    override init(frame: CGRect) {
        // 1. setup any properties here
    
        // 2. call super.init(frame:)
        super.init(frame: frame)
    
        // 3. Setup view from .xib file
        xibSetup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        // 1. setup any properties here
    
        // 2. call super.init(coder:)
        super.init(coder: aDecoder)
    
        // 3. Setup view from .xib file
        xibSetup()
    }
    func xibSetup() {
        view = loadViewFromNib()
    
        // use bounds not frame or it'll be offset
        view.frame = bounds
    
        // Make the view stretch with containing view
        view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
    
        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(view)
    }
    
    func loadViewFromNib()->UIView{
    
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "KeyCell", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
        }
    
    }
    

    再在KeyboardView里面加上音标

    var keys = keys = ["ɪ","ə","ɒ","ʊ","ʌ","e","æ","i:","ɜ:","ɔ:","u:","ɑ:","eɪ","aɪ","ɔɪ","aʊ","əʊ","ɪə","ɛə","ʊə","p","t","k","f","θ","s","ts","tr","∫","t∫","b","d","g","v","ð","z","dz","dr","ʒ","dʒ","m","n","ŋ","h","l","r","j","w"]
    

    加上collectionView的dataSource和delegate.我这里的keyboardView继承的是UIControl,在collectionView的collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)方法里使用 sendActionsForControlEvents(.ValueChanged)来处理交互相应
    整个keyboardView的代码如下:(这里处理点击文本较为粗糙,直接使用一个currentText)

        //
    //  KBV.swift
    //  timelineDEMO
    //
    //  Created by 薛定谔弄死的猫 on 16/3/21.
    //  Copyright © 2016年 薛定谔弄死的猫. All rights reserved.
    //
    
    import UIKit
    
    @IBDesignable class KBV: UIControl,UICollectionViewDataSource,UICollectionViewDelegate {
    
    var view :UIView!
    var keys = [String]()
    var currentText = ""
    
    @IBOutlet weak var collectionView: UICollectionView!
    
    @IBAction func deleteInput(sender: AnyObject) {
        currentText = "DELETE"
        sendActionsForControlEvents(.ValueChanged)
    }
    
    override init(frame: CGRect) {
        // 1. setup any properties here
        // 2. call super.init(frame:)
        super.init(frame: frame)
    
        // 3. Setup view from .xib file
        xibSetup()
    }
    
    required init?(coder aDecoder: NSCoder) {
        // 1. setup any properties here
    
        // 2. call super.init(coder:)
        super.init(coder: aDecoder)
    
        // 3. Setup view from .xib file
        xibSetup()
    }
    func xibSetup() {
        view = loadViewFromNib()
    
        // use bounds not frame or it'll be offset
        view.frame = bounds
    
        // Make the view stretch with containing view
        view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
    
        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(view)
        setUpUI()
    }
    
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return keys.count
    }
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath)
        (cell.viewWithTag(1)as!UILabel).text = keys[indexPath.row]
    
        return cell
    }
    
    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        print("input IPA:\(keys[indexPath.row])")
        self.currentText = keys[indexPath.row]
        sendActionsForControlEvents(.ValueChanged)
    }
    
    func loadViewFromNib()->UIView{
    
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "KBV", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }
    
    func setUpUI(){
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.registerClass(KeyCell.self, forCellWithReuseIdentifier: "cell")
        setUpKeys()
    
    }
    
    func setUpKeys()  {
    
        keys = ["ɪ","ə","ɒ","ʊ","ʌ","e","æ","i:","ɜ:","ɔ:","u:","ɑ:","eɪ","aɪ","ɔɪ","aʊ","əʊ","ɪə","ɛə","ʊə","p","t","k","f","θ","s","ts","tr","∫","t∫","b","d","g","v","ð","z","dz","dr","ʒ","dʒ","m","n","ŋ","h","l","r","j","w"]
        collectionView.reloadData()
    }
    
    @IBAction func changeKB(sender: AnyObject) {
        currentText = "NextKeyboard"
        sendActionsForControlEvents(.ValueChanged)
    }
    }
    
    
  4. 设置KeyboardViewController

    1. 设置键盘约束

      override func updateViewConstraints() {
      super.updateViewConstraints()
      
      let constraints1 = NSLayoutConstraint(item: keyboardView, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: 0)
      let constraints2 = NSLayoutConstraint(item: keyboardView, attribute: .Leading, relatedBy: .Equal, toItem: view, attribute: .Leading, multiplier: 1, constant: 0)
      let constraints3 = NSLayoutConstraint(item: keyboardView, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1, constant: 0)
      let constraints4 = NSLayoutConstraint(item: keyboardView, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1, constant: 0)
      view.addConstraints([constraints1,constraints2,constraints3,constraints4])
      
      // Add custom view sizing constraints here
      }
      
    2. 监听keyboardView的ValueChanged:keyboardView.addTarget(self, action: "handleInput", forControlEvents: .ValueChanged)

    3. 处理点击按钮

          func handleInput()  {
      let proxy = textDocumentProxy
      
      switch keyboardView.currentText {
      case "DELETE":
          print("DELETE")
          proxy.deleteBackward()
      
      case "NextKeyboard" :
          print("NextKeyboard")
          advanceToNextInputMode()
      
      default:
          print(keyboardView.currentText)
          proxy.insertText(keyboardView.currentText)
      
      }
      }
      

      4.删除默认的那个及其相关代码
      swift
      @IBOutlet var nextKeyboardButton: UIButton!

    至此这个粗糙的音标键盘就完成了

    效果

    注意不要忘了在设置里把键盘添加上

    长按地球图标切换键盘:

动态演示:
IPAKeyboard

The End

也许是时候,写一个表情包输入法了🌝🌚