iOS-自定义表情键盘

demo地址

效果图

1. 逻辑分析

  • 素材就是一些emoji 的字符串和一些表情图片资源, 打包成Emoticons.bundle放在demo中了.
  • 可以先将demo 下载下来, 对照bundle 文件更好理解.
/* 自定义表情键盘  

 - 表情数据结果分析
    - 分为几种表情(4种)
        - 最近 (1页表情, 20个)
            - [20]
        - 默认 (6页表情, 108个)
            - [20] [20] [20] [20] [20] [8]
        - emoji (4页表情, 80个)
            - [20] [20] [20] [20]
        - lxh (2页表情, 40个)
            - [20] [20]
 
    - 分析collectionView 怎么显示
        - 最近
            - 1 页
        - 默认
            - 6 页
        - emoji
            - 4 页
        - lxh
            - 2 页
    ? - 怎么确定collectionView 的组数?
        - [[[20]], [[20] [20] [20] [20] [20] [8]], [[20] [20] [20] [20]], [[20] [20]]].count = 4组.
        - 也就是section 数量 = 三维数组.count
        - 每一个section 的item 数量 = 三维数组[单个元素].count(也就是 二维数组.count).
        - 而每一个item 上有20 个表情控件.
 */

2. 层级结构分析(由于Xcode渲染不出来键盘, 所以在文末有一张灵魂手绘, 希望能对理解历来有所帮助吧.)

2.1 图层结构分析
- 第一层级 viewController.view
    - 第二层级
        - UITextView (系统键盘)
        - 笑脸View (目的: 点击切换键盘)
        
        - 第三层级
            - 自定义的inputView, 来替换系统的键盘 (也就是自定义的表情键盘, 替换UITextView 的系统键盘)
            - 自定义的inputView 有哪些子控件呢?
                - UICollectionView, 承载那些小表情
                - 分组的View, 代表每一大组
2.2 自定义的inputView 中UICollectionView 的分析
/* inputView 的UICollectionView
 - 组数代表有多少不同款的表情
    - 款式数的总量, 就是底部分组栏 有多少个分组的按钮(最近、默认、emoji、等等)
 
- 每一个item 的size 都是inputView 的宽度, 高度 = inputView - 底部分组栏的高度
    - 每一个item 上面可承载(七列三行) 7*3 - 1(删除按钮) = 20 个button
    - 因此, 每一种类别拥有所有表情的个数 / 20 = 每一组item 的数量
*/

3. 代码实现

文件夹说明
3.1 切换系统键盘和自定义的键盘
/* UITextView 自带的inputView 属性, 可以重新赋值来切换
 - customTextView 为自定义的UITextView
 - emoticonKeyboardView 为自己自定义的表情键盘, 继承自UIView
 */
func switchKeyboard(){
    // 如果inputView == nil 就代表是系统键盘 改成自定义键盘
    if self.customTextView.inputView == nil {
        self.customTextView.inputView = self.emoticonKeyboardView
    }else {
        // 如果inputView != nil 就代表你设置了自定义键盘 改成系统键盘
        self.customTextView.inputView = nil
    }
    // 开启第一响应
    self.customTextView.becomeFirstResponder()
    // 切换inputView后要刷新
    self.customTextView.reloadInputViews()
}
3.2 获取本地bundle文件, 加载其中的资源
  • 因为可能用到键盘的地方较多, 避免每次调出键盘都去访问磁盘资源, 这里做了一个单例, 将磁盘资源加载到缓存, 方便使用.
    // 获取表情bundle
    lazy var emoticonBundle:Bundle = {
        // 路径
        let path = Bundle.main.path(forResource: "Emoticons.bundle", ofType: nil)!
        // 获取bundle
        let bundle = Bundle(path: path)!
        return bundle
    }()
    // MARK: 根据bundle 文件中对应的文件名称不同, 获取不同名称下的资源
    // 通过该方法分别获取不同的表情包的 一维数组
    func loadSingledimensionalEmoticonsArray(name: String) -> [HEmoticonModel]{
        // 路径
        let file = emoticonBundle.path(forResource: "\(name)/info.plist", ofType: nil)!
        // plist 数组
        let plistArray = NSArray(contentsOfFile: file)!
        // 创建临时可变字典
        var tempArray: [HEmoticonModel] = [HEmoticonModel]()
        // 字典转模型
        for dict in plistArray {
            let model = HEmoticonModel(dict: dict as! [String : Any])
            // 给path 赋值
            model.path = "\(name)" + "/" + "\(model.png ?? "")"
            tempArray.append(model)
        }
        return tempArray
    }
3.3 一维数组转二维数组
// 通过该方法把一维数组转成 二维数组
    func loadTwoDimensionalEmoticonsArray(emoticons: [HEmoticonModel]) -> [[HEmoticonModel]]{
        
        // 得到一维数组将要在表情键盘显示的页数
        let pageCount = (emoticons.count + HEMOTICONMAXCOUNT - 1)/HEMOTICONMAXCOUNT
        
        // 创建一个二维数组可变的 空数组
        var groupArray: [[HEmoticonModel]] = [[HEmoticonModel]]()
        
        for i in 0.. emoticons.count {
                len = emoticons.count - loc
            }
            // 范围
            let range = NSRange(location: loc, length: len)
            // 截取数组 -> NSArray 的方法, 通过起始位置和每次截取的数量, 来获取子数组
            let tempArray = (emoticons as NSArray).subarray(with: range) as! [HEmoticonModel]
            // 添加到二维数组中
            groupArray.append(tempArray)
        }
        // 返回
        return groupArray
    }
3.4 UICollectionView 数据源方法的return 值
    func numberOfSections(in collectionView: UICollectionView) -> Int {
        // 三维数组.count 即为组数
        return HEmoticonTools.shared.allEmoticons.count
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 二维数组.count 即为item数
        return HEmoticonTools.shared.allEmoticons[section].count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: pageViewCellId, for: indexPath) as! HEmoticonPageViewCell
        cell.indexPath = indexPath // 测试使用
        // 赋值 -> 一维数组赋值
        cell.emoticons = HEmoticonTools.shared.allEmoticons[indexPath.section][indexPath.item]
        return cell
    }

4. '最近使用' 分组表情和表情上屏分析

  • '最近使用' 分组的数据是记录当前用户最近使用过的表情, 存入了沙盒, 实时刷新.
// 保存表情模型 -> 为 最近表情 提供数据
    func saveRecentModel(emoticonModel: HEmoticonModel){
        
        // 遍历当前的最近表情的数组 -> 去重 -> 有一样的先移除, 然后添加到最后
        for (i, model) in recentEmoticons.enumerated() {
            // 判断你的类型 emoji 还是 图片
            if model.isEmoji {
                // emoji
                if model.code == emoticonModel.code {
                    recentEmoticons.remove(at: i)
                }
            }else {
                //图标表情
                if model.png == emoticonModel.png {
                    recentEmoticons.remove(at: i)
                }
            }
        }
        
        // 添加到最近表情数组中
        recentEmoticons.insert(emoticonModel, at: 0)
        // 判断如果超过20个 干掉最后一个
        if recentEmoticons.count > 20 {
            recentEmoticons.removeLast()
        }
        
        // 三维数组中的最近表情的数组进行更改
        allEmoticons[0][0] = recentEmoticons
        
        // 保存到沙盒中
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: recentEmoticons, requiringSecureCoding: false)
            do {
                _ = try data.write(to: URL(fileURLWithPath: file))
                print("最近表情写入成功")
            } catch {
                print("最近表情写入本地失败: \(error)")
            }
        } catch {
            
        }
    }
  • 表情上屏是用的原生富文本实现的, 直接赋值富文本属性即可.

附知识点二:
层级关系

.End

你可能感兴趣的:(iOS-自定义表情键盘)