13-1图文混排

2016-05-29  本文已影响233人  月下独酌灬

图文混排

实现效果

表情图文混排.png.jpeg

表情按钮点击事件

/// 添加表情按钮
private func addEmoticonButtons(){
    for _ in 0..<HMEmoticonPageNum {
        let button = UIButton()
        // 添加点击事件
        button.addTarget(self, action: "emoticonButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
        // 设置字体大小
        button.titleLabel?.font = UIFont.systemFontOfSize(36)
        contentView.addSubview(button)
        emoticonButtons.append(button)
    }
}
/// 表情按钮点击
///
/// - parameter button: <#button description#>
@objc private func emoticonButtonClick(button: UIButton) {
    printLog("表情按钮点击了")
}
class HMEmoticonButton: UIButton {

    var emoticon: HMEmoticon?
}
// 装有所有表情按钮的集合
private lazy var emoticonButtons: [HMEmoticonButton] = [HMEmoticonButton]()
// 遍历当前设置的表情数据
for (index,value) in emoticons!.enumerate() {
    let button = emoticonButtons[index]
    // 设置表情属性
    button.emoticon = value
    // 显示当前遍历到的表情按钮
    button.hidden = false
    if !value.isEmoji {
        let image = UIImage(named: "\(value.path!)/\(value.png!)")
        button.setImage(image, forState: UIControlState.Normal)
        button.setTitle(nil, forState: UIControlState.Normal)
    }else{
        button.setImage(nil, forState: UIControlState.Normal)
        button.setTitle((value.code! as NSString).emoji(), forState: UIControlState.Normal)
    }
}
var emoticon: HMEmoticon? {
    didSet{
        // 显示表情数据
        if !emoticon!.isEmoji {
            let image = UIImage(named: "\(emoticon!.path!)/\(emoticon!.png!)")
            self.setImage(image, forState: UIControlState.Normal)
            self.setTitle(nil, forState: UIControlState.Normal)
        }else{
            self.setImage(nil, forState: UIControlState.Normal)
            self.setTitle((emoticon!.code! as NSString).emoji(), forState: UIControlState.Normal)
        }
    }
}
/// 当前页显示的表情数据
var emoticons: [HMEmoticon]? {
    didSet{

        // 先隐藏所有的表情按钮
        for value in emoticonButtons {
            value.hidden = true
        }

        // 遍历当前设置的表情数据
        for (index,value) in emoticons!.enumerate() {
            let button = emoticonButtons[index]
            // 设置表情属性
            button.emoticon = value
            // 显示当前遍历到的表情按钮
            button.hidden = false
        }
    }
}
// 表情按钮点击通知
let HMEmoticonDidSelectedNotification = "HMEmoticonDidSelectedNotification"
/// 表情按钮点击
@objc private func emoticonButtonClick(button: HMEmoticonButton) {
    //发送表情按下的通知
    NSNotificationCenter.defaultCenter().postNotificationName(HMEmoticonDidSelectedNotification, object: self, userInfo: ["emoticon": button.emoticon!])
}
// 监听表情按钮点击的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "emoticonDidSelected:", name: HMEmoticonDidSelectedNotification, object: nil)
/// 表情按钮点击发送通知监听的方法
@objc private func emoticonDidSelected(noti: NSNotification){
    // 需要重写 `HMEmoticon` 的 description 属性
    printLog(noti.userInfo!["emoticon"])
}

运行测试

注意区分 emoji 表情与图片表情

/// 表情按钮点击发送通知监听的方法
@objc private func emoticonDidSelected(noti: NSNotification){
    printLog(noti.userInfo!["emoticon"])
    // 判断 emoticon 是否为空
    guard let emoticon = noti.userInfo!["emoticon"] as? HMEmoticon else {
        return
    }

    if !emoticon.isEmoji {
        // 通过原有的文字初始化一个可变的富文本
        let originalAttributedString = NSMutableAttributedString(attributedString: textView.attributedText)

        // 通过表情模型初始化一个图片
        let image = UIImage(named: "\(emoticon.path!)/\(emoticon.png!)")
        // 初始化文字附件,设置图片
        let attatchment = NSTextAttachment()
        attatchment.image = image

        // 通过文字附件初始化一个富文本
        let attributedString = NSAttributedString(attachment: attatchment)
        // 添加到原有的富文本中
        originalAttributedString.appendAttributedString(attributedString)

        // 设置 textView 的 attributedText
        textView.attributedText = originalAttributedString
    }else{
        // emoji 表情
    }
}

运行测试:图片太大

// 图片宽高与文字的高度一样
let imageWH = textView.font!.lineHeight
// 调整图片大小
attatchment.bounds = CGRectMake(0, 0, imageWH, imageWH)

运行测试:当输入第二个表情的时候图片大小变小了,没有指定 attributedString 的字体大小

// 通过文字附件初始化一个富文本
let attributedString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: attatchment))
// 设置添加进去富文本的字体大小
attributedString.addAttribute(NSFontAttributeName, value: textView.font!, range: NSMakeRange(0, 1))

运行测试:发现表情图片偏上,调整 attachmentbounds

// 调整图片大小 --> 解决图片大小以及偏移问题
attatchment.bounds = CGRectMake(0, -4, imageWH, imageWH)

运行测试:发现当光标不在最后一位的时候,表情图片依然拼在最后面,解决办法就是调用 NSMutableAttributedStringinsertAttributedString 的方法,传入 index 就是当前 textView 的选中范围的 location

// 添加到原有的富文本中
// originalAttributedString.appendAttributedString(attributedString)
// 解决当光标不在最后一位的时候添加图片表情的问题
let selectedRange = textView.selectedRange
originalAttributedString.insertAttributedString(attributedString, atIndex: selectedRange.location)

运行测试:当添加图片到光标位置的时候,光标移动到最后一个去了,解决方法:在设置完 textView 的富文本之后调用 selectedRange

var selectedRange = textView.selectedRange
originalAttributedString.insertAttributedString(attributedString, atIndex: selectedRange.location)

// 设置 textView 的 attributedText
textView.attributedText = originalAttributedString
// 更新光标所在位置
selectedRange.location += 1
textView.selectedRange = selectedRange

运行测试:如果选中某一段字符,然后再次输入表情的话,需要用表情把选中的字符替换掉

// 添加到原有的富文本中
// originalAttributedString.appendAttributedString(attributedString)
var selectedRange = textView.selectedRange
// 解决当光标不在最后一位的时候添加图片表情的问题
// originalAttributedString.insertAttributedString(attributedString, atIndex: selectedRange.location)
// 解决 textView 选中文字之后输入表情产生的 bug
originalAttributedString.replaceCharactersInRange(selectedRange, withAttributedString: attributedString)

// 设置 textView 的 attributedText
textView.attributedText = originalAttributedString
// 更新光标所在位置,以及选中长度
selectedRange.location += 1
selectedRange.length = 0
textView.selectedRange = selectedRange

运行测试

if !emoticon.isEmoji {
    ...
}else{
    // emoji 表情
    textView.insertText((emoticon.code! as NSString).emoji())
}

运行测试

deleteButton.addTarget(self, action: "deleteButtonClick:", forControlEvents: UIControlEvents.TouchUpInside)
// 删除按钮点击通知
let HMEmoticonDeleteButtonDidSelectedNotification = "HMEmoticonDeleteButtonDidSelectedNotification"
@objc private func deleteButtonClick(button: UIButton){
    //发送表情按下的通知
    NSNotificationCenter.defaultCenter().postNotificationName(HMEmoticonDeleteButtonDidSelectedNotification, object: self)
}
// 监听删除按钮的通知
NSNotificationCenter.defaultCenter().addObserver(self, selector: "deletedButtonSelected:", name: HMEmoticonDeleteButtonDidSelectedNotification, object: nil)
// 删除按钮点击的通知
@objc private func deletedButtonSelected(noti: NSNotification){
    textView.deleteBackward()
}

运行测试

class HMEmoticonTextView: HMTextView {

    /// 向当前 textView 添加表情
    ///
    /// - parameter emoticon: 表情模型
    func insertEmoticon(emoticon: HMEmoticon) {

    }
}
/// 输入框
private lazy var textView: HMEmoticonTextView = {
    let textView = HMEmoticonTextView()
    textView.placeholder = "听说下雨天音乐和辣条更配哟~"
    textView.font = UIFont.systemFontOfSize(16)
    textView.alwaysBounceVertical = true
    textView.delegate = self
    return textView
}()
// 向当前 textView 添加表情
///
/// - parameter emoticon: 表情模型
func insertEmoticon(emoticon: HMEmoticon) {
    if !emoticon.isEmoji {
        // 通过原有的文字初始化一个可变的富文本
        let originalAttributedString = NSMutableAttributedString(attributedString: attributedText)

        // 通过表情模型初始化一个图片
        let image = UIImage(named: "\(emoticon.path!)/\(emoticon.png!)")
        // 初始化文字附件,设置图片
        let attatchment = NSTextAttachment()
        attatchment.image = image
        // 图片宽高与文字的高度一样
        let imageWH = font!.lineHeight
        // 调整图片大小 --> 解决图片大小以及偏移问题
        attatchment.bounds = CGRectMake(0, -4, imageWH, imageWH)

        // 通过文字附件初始化一个富文本
        let attributedString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: attatchment))
        // 设置添加进去富文本的字体大小
        attributedString.addAttribute(NSFontAttributeName, value: font!, range: NSMakeRange(0, 1))

        // 添加到原有的富文本中
        //            originalAttributedString.appendAttributedString(attributedString)
        var selectedRange = self.selectedRange
        // 解决当光标不在最后一位的时候添加图片表情的问题
        //            originalAttributedString.insertAttributedString(attributedString, atIndex: selectedRange.location)
        // 解决 textView 选中文字之后输入表情产生的 bug
        originalAttributedString.replaceCharactersInRange(selectedRange, withAttributedString: attributedString)

        // 设置 textView 的 attributedText
        attributedText = originalAttributedString
        // 更新光标所在位置,以及选中长度
        selectedRange.location += 1
        selectedRange.length = 0
        self.selectedRange = selectedRange

    }else{
        // emoji 表情
        insertText((emoticon.code! as NSString).emoji())
    }
}
/// 表情按钮点击发送通知监听的方法
@objc private func emoticonDidSelected(noti: NSNotification){
    // 判断 emoticon 是否为空
    guard let emoticon = noti.userInfo!["emoticon"] as? HMEmoticon else {
        return
    }
    textView.insertEmoticon(emoticon)
}

运行测试:当输入图片表情的时候,占位文字并没有隐藏,解决方法,在 insertEmoticon 方法最后调用代理,发送通知

// 调用代理
// OC 写法
// if let del = self.delegate where del.respondsToSelector("textViewDidChange:"){
//     del.textViewDidChange!(self)
// }

// Swift 写法
self.delegate?.textViewDidChange?(self)
// 发送通知
NSNotificationCenter.defaultCenter().postNotificationName(UITextViewTextDidChangeNotification, object: self)

运行测试

表情点击气泡

实现效果

表情点击气泡.png.jpeg

点击表情按钮弹出一个气泡

实现思路

  1. 气泡可以使用 xib 实现
  2. 点击表情按钮的时候取到对应表情按钮的位置
  3. 将位置转化成在 window 上的位置
  4. 根据将气泡添加到最上层的 Window 上
  5. 0.1 秒之后气泡从 window 上移除

代码实现

popviewxib.png.jpeg

将此 View 的背景设置成透明色,并将 button 的类型设置成 HMEmoticonButton

class HMEmoticonPopView: UIView {

    @IBOutlet weak var emoticonButton: HMEmoticonButton!

    class func popView() -> HMEmoticonPopView {
        let result = NSBundle.mainBundle().loadNibNamed("HMEmoticonPopView", owner: nil, options: nil).last! as! HMEmoticonPopView
        return result
    }
}
// MARK: - 监听事件

@objc private func emoticonButtonClick(button: HMEmoticonButton){
    printLog("表情按钮点击")
    if let emoticon = button.emoticon {
        ...
        // 初始化 popView
        let popView = HMEmoticonPopView.popView()

        // 将 popView 添加到 window 上
        let window = UIApplication.sharedApplication().windows.last!
        window.addSubview(popView)

        // 0.1 秒消失
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
            popView.removeFromSuperview()
        }
    }
}
let rect = button.convertRect(button.bounds, toView: nil)
popView.centerX = CGRectGetMidX(rect)
popView.y = CGRectGetMaxY(rect) - popView.height

运行测试

var emoticon: HMEmoticon? {
    didSet{
        emoticonButton.emoticon = emoticon
    }
}
/// 将传入的 PopView 显示在当前按钮之上
///
/// - parameter popView: popView
func showPopView(popView: HMEmoticonPopView){
    // 获取到 button 按钮在屏幕上的位置
    let rect = convertRect(bounds, toView: nil)
    // 设置位置
    popView.centerX = CGRectGetMidX(rect)
    popView.y = CGRectGetMaxY(rect) - popView.height
    // 设置表情数据
    popView.emoticon = emoticon
    // 添加到 window 上
    let window = UIApplication.sharedApplication().windows.last!
    window.addSubview(popView)
}
@objc private func emoticonButtonClick(button: HMEmoticonButton){
    printLog("表情按钮点击")
    if let emoticon = button.emoticon {
        ...
        // 初始化 popView
        let popView = HMEmoticonPopView.popView()
        // 显示 popView
        button.showPopView(popView)
        // 0.25 秒消失
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.25 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
            popView.removeFromSuperview()
        }
    }
}

长按滑动的时候气泡随着手指移动

实现思路

  1. 懒加载一个 popView 供长按拖动的时候显示
  2. 监听 cell 的长按 -> 添加长按手势
  3. 在手势监听方法里面取到手指的位置
  4. 判断手指的位置在哪一个按钮之上
  5. 调用对应按钮的 showPopView 方法
  6. 在手势结束的时候隐藏 popView

代表实现

/// 长按显示的 popView
private lazy var popView = HMEmoticonPopView.popView()
// 添加长按手势事件
let longGes = UILongPressGestureRecognizer(target: self, action: "longPress:")
contentView.addGestureRecognizer(longGes)
/// 长按手势监听
///
/// - parameter ges: 手势
@objc private func longPress(ges: UILongPressGestureRecognizer) {
    // 获取当前手势在指定 view 上的位置
    let location = ges.locationInView(contentView)
    printLog(location)
}
/// 长按手势监听
///
/// - parameter ges: 手势
@objc private func longPress(ges: UILongPressGestureRecognizer) {

    /// 根据位置查找到对应位置的按钮
    ///
    /// - parameter location: 位置
    func findButtonWithLocation(location: CGPoint) -> HMEmoticonButton? {
        for value in emoticonButtons {
            if CGRectContainsPoint(value.frame, location) {
                return value
            }
        }
        return nil
    }
    // 获取当前手势在指定 view 上的位置
    let location = ges.locationInView(contentView)
}
switch ges.state {
case .Began,.Changed:
    // 通过手势的位置查找到对应的按钮
    guard let button = findButtonWithLocation(location) where button.hidden == false else {
        return
    }
    popView.hidden = false
    button.showPopView(popView)
case .Ended:
    popView.hidden = true
    // 通过手势的位置查找到对应的按钮
    guard let button = findButtonWithLocation(location) where button.hidden == false else {
        return
    }
    emoticonButtonClick(button)
default:
    // 将 popView 隐藏
    popView.hidden = true
    break
}
上一篇下一篇

猜你喜欢

热点阅读