美文网首页
iOS-自定义表情键盘

iOS-自定义表情键盘

作者: 下班不写程序 | 来源:发表于2020-04-14 21:30 被阅读0次

    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..<pageCount{
                // 位置: 截取子数组的起始页数, 与HEMOTICONMAXCOUNT 做积, 代表从一位数组的那个位置开始截取
                let loc = i * HEMOTICONMAXCOUNT
                // 长度: 将要截取的子数组的长度
                var len = HEMOTICONMAXCOUNT
                // 反之越界
                if len + loc > 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-自定义表情键盘

          本文链接:https://www.haomeiwen.com/subject/wvptvhtx.html