14- 首页完善

首页完善

实现功能

  • 首页表情显示
  • 特殊字符高亮显示
  • 特殊字符点击处理

首页表情显示

步骤

  1. 将微博内容字符串生成一个 NSMutableAttributedString
  • 需要匹配到表情字符串
  • 通过表情字符找到对应表情模型
  • 通过表情模型生成富文本(NSAttributedString)
  • 将第 1 步里面的表情字符串替换成表情的富文本
  • 将以上结果设置微博内容的 UILabelattributedString

代码实现

  • RegexKitLite

    • 拖入 RegexKitLite 正则匹配第三方库
    • 导入 libicucure 框架
    • 设置 RegexKitLite.mCompiler Flags-fno-objc-arc (指定以 MRC 模式编译文件)
  • HMStatusViewModel 添加 dealStatusText 方法,处理微博内容字符串

private func dealStatusText(statusText: String?) -> NSMutableAttributedString? {
    return nil
}
  • 匹配表情字符串
    • 表达式:"\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]"
private func dealStatusText(statusText: String?) -> NSMutableAttributedString? {
    // 匹配表情字符串
    guard let text = statusText as NSString? else {
        return nil
    }

    /**
        第一个参数:正则表达式
        第二个参数:闭包,其参数:
            captureCount: 捕获个数
            captureString: 捕获的 String,指针
            captureRange: 捕获的 范围,指针
            stop: 是否停止捕获,指针
    */
    text.enumerateStringsMatchedByRegex("\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]") { (captureCount, captureString, captureRange, stop) -> Void in
        printLog(captureString.memory)
    }
    return nil
}
  • HMStatusViewModelsetStatus 方法中测试以上方法
/// 一些计算的逻辑
private func setStatus(){
    ...
    dealStatusText(status?.text)
}
  • 定义 HMMatchResult
class HMMatchResult: NSObject {

    var captureString: String?
    var captureRange: NSRange?

    init(captureString: String, captureRange: NSRange) {
        self.captureRange = captureRange
        self.captureString = captureString
        super.init()
    }

}
  • 使用数组保存匹配结果
// 利用数组保存匹配结果
var matchResults = [HMMatchResult]()

text.enumerateStringsMatchedByRegex("\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]") { (captureCount, captureString, captureRange, stop) -> Void in
    printLog(captureString.memory)

    let matchResult = HMMatchResult(captureString: captureString.memory! as String, captureRange: captureRange.memory)
    matchResults.append(matchResult)
}
  • 通过表情字符串获取到表情模型
    • HMEmoticonTools 中添加方法 emoticonWithChs:
/// 通过表情描述文字查找到对应有表情
///
/// - parameter chs: 表情描述文字
///
/// - returns: 表情模型
func emoticonWithChs(chs: String) -> HMEmoticon? {

    for emoticon in defaultEmoticons {
        if emoticon.chs == chs {
            return emoticon
        }
    }
    for emoticon in lxhEmoticons {
        if emoticon.chs == chs {
            return emoticon
        }
    }
    return nil
}
  • 反转遍历数组,替换表情字符串
private func dealStatusText(statusText: String?) -> NSMutableAttributedString? {
    // 匹配表情字符串
    guard let text = statusText as NSString? else {
        return nil
    }
    // 将内容转成富文本
    let result = NSMutableAttributedString(string: text as String)

    // 利用数组保存匹配结果
    var matchResults = [HMMatchResult]()

    text.enumerateStringsMatchedByRegex("\\[[a-zA-Z0-9\\u4e00-\\u9fa5]+\\]") { (captureCount, captureString, captureRange, stop) -> Void in
        printLog(captureString.memory)

        let matchResult = HMMatchResult(captureString: captureString.memory! as String, captureRange: captureRange.memory)
        matchResults.append(matchResult)
    }


    // 反转遍历匹配结果
    // 为什么要返转遍历替换:原因就是如果从前往后替换的话会出现越界异常
    for matchResult in matchResults.reverse() {

        let emoticon = HMEmoticonTools.emoticonWithChs(matchResult.captureString! as String)

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

            // 通过文字附件初始化一个富文本
            let attributedString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: attatchment))

            result.replaceCharactersInRange(matchResult.captureRange!, withAttributedString: attributedString)
        }
    }

    // 设置整个富文本的字体大小
    result.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(HMStatusContentFontSize), range: NSMakeRange(0, result.length))

    return result
}
  • 定义原创微博富文本属性,记录转换之后的原创微博富文本
/// 原创微博正文内容富文本
var originalStatusAttributedString: NSMutableAttributedString?


/// 一些计算的逻辑
private func setStatus(){
    // 来源字符串
    sourceText = dealSourceText(status?.source)
    originalStatusAttributedString = dealStatusText(status?.text)
}
  • HMStatusOriginalView 中设置原创微博内容
/// 微博视图模型
var statusViewModel: HMStatusViewModel? {
    didSet{
        ...
        contentLabel.attributedText = statusViewModel?.originalStatusAttributedString
        ...
        }
    }
}

运行测试

  • 添加转发微博富文本属性,记录转换之后的转发微博富文本
var retweetStatusAttributedString: NSMutableAttributedString?


/// 一些计算的逻辑
private func setStatus(){
    // 来源字符串
    sourceText = dealSourceText(status?.source)
    originalStatusAttributedString = dealStatusText(status?.text)
    retweetStatusAttributedString = dealStatusText(retweetText)
}
  • HMStatusRetweetView 中设置转发微博的内容
/// 微博视图模型
var statusViewModel: HMStatusViewModel?{
    didSet{
        contentLabel.attributedText = statusViewModel!.retweetStatusAttributedString
        ...
    }
}

运行测试

特殊字符高亮显示

实现步骤:

  • 在获取到表情的富文本的基础之上,匹配到对应特殊字符
  • 在给富文本中特殊字符添加颜色的属性

代码实现

  • 匹配 @xxx

    • 匹配规则:@ 后面不能出现空格、标点之类不是字母数字下划线汉字的字符
    • 所以正则表达式为:@[^\\W]+
  • 添加 addHighLightAttr: 方法

/// 给特殊字符添加颜色
///
/// - parameter attrString: 需要添加特殊字符高亮的富文本
private func addHighLightedAttr(attrString: NSMutableAttributedString) {

    // 匹配特殊字符串并高亮
}
  • dealStatusText 方法中返回结果之前调用此方法
private func dealStatusText(statusText: String?) -> NSMutableAttributedString? {
    ...

    // 添加特殊字符高亮
    addHighLightedAttr(result)

    // 设置整个富文本的字体大小
    result.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(HMStatusContentFontSize), range: NSMakeRange(0, result.length))

    return result
}
  • addHighLightedAttr: 中匹配 @xxx
// 匹配 `@`
(attrString.string as NSString).enumerateStringsMatchedByRegex("@[^\\s^:^,]+") { (captureCount, captureString, captureRange, stop) -> Void in
    attrString.addAttribute(NSForegroundColorAttributeName, value: RGB(r: 80, g: 125, b: 175), range: captureRange.memory)
}
  • 匹配话题(#)
    • 匹配规则:# 与 # 中间不能出现 #
    • 正则表达式为:#[^#]+#
// 匹配 `#`
(attrString.string as NSString).enumerateStringsMatchedByRegex("#[^#]+#") { (captureCount, captureString, captureRange, stop) -> Void in
    attrString.addAttribute(NSForegroundColorAttributeName, value: RGB(r: 80, g: 125, b: 175), range: captureRange.memory)
}
  • 匹配 url
    • 匹配规则:http:// 后面不能跟空格和汉字
    • 正则表达式为:http(s)?://[^\\s^\\u4e00-\\u9fa5]+
// 匹配 `url`
(attrString.string as NSString).enumerateStringsMatchedByRegex("http(s)?://[^\\s^\\u4e00-\\u9fa5]+") { (captureCount, captureString, captureRange, stop) -> Void in
    attrString.addAttribute(NSForegroundColorAttributeName, value: RGB(r: 80, g: 125, b: 175), range: captureRange.memory)
}

特殊字符点击处理

实现思路

  • 获取到手指在控件上点击的点
    • 监听 touchBegin 方法
  • 获取到那个点对应的字符串范围
    • UITextView 身上有此方法
  • 获取到此控件中所有的特殊字符串的范围
    • 在匹配特殊字符串的时候保存特殊字符串的范围
  • 遍历查询手指点击对应的字符串范围是在哪一个特殊字符串的范围内容
    • for 循环遍历,使用 NSLocationInRange 的方法判断某个位置是否在某个范围之内
  • 如果查询到,将其高亮
    • 可以给对应范围添加一个背景颜色的属性或者直接在对应位置添加子视图

代码实现

  • 新建 HMStatusLabel 继承于 UILabel
class HMStatusLabel: UILabel {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupUI()
    }

    private func setupUI(){

    }
}

  • 将转发微博View HMStatusRetweetView 中显示文字的 label 设置成 HMStatusLabel
private lazy var contentLabel: HMStatusLabel = HMStatusLabel(textColor: UIColor.darkGrayColor(), fontSize: HMStatusContentFontSize, maxLayoutWidth: UIScreen.mainScreen().bounds.width - 2 * HMStatusCellMargin)
  • HMStatusLabel 内部添加一个 UITextView 并添加约束
class HMStatusLabel: UILabel {

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
        userInteractionEnabled = true;
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupUI()
    }

    private func setupUI(){
        addSubview(textView)
        textView.snp_makeConstraints { (make) -> Void in
            make.edges.equalTo(self)
        }
    }
    // MARK: - 懒加载控件
    private lazy var textView: UITextView = {
        let textView = UITextView()
        textView.alpha = 0.0
        return textView
    }()
}
  • 重写 HMStatusLabelattributedText 属性,在赋值的时候设置 textView 的 attributedText
override var attributedText: NSAttributedString? {
    didSet{
        textView.attributedText = attributedText
    }
}

运行测试:发现 textView 显示的内容与 label 显示的内容没有对齐,原因是 textView 里面的内容默认有一个间距

  • 设置 textView 的 textContainerInset
textView.textContainerInset = UIEdgeInsetsMake(0, -5, 0, -5)
  • 重写 touchesBegan 方法,在方法内部取到当前用户点击的点
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch = touches.first!
    let location = touch.locationInView(self)
    print(location)
}
  • 通过点取到用户点击的字符范围
// 获取到用户点击对应的字符范围
let textRange = textView.characterRangeAtPoint(location)
textView.selectedTextRange = textRange
let range = textView.selectedRange
print(range)
  • 问题:需要获取到当前 label 里面特殊文字的范围
    • HMStatusViewModel 中添加原创微博以及转发微博特殊文字的匹配结果数组
// 转发微博匹配内容
var retweetMatchResults: [HMMatchResult]?
// 原创微博匹配内容
var originalMatchResults: [HMMatchResult]?
  • 更改 dealStatusText 方法返回值 元组
private func dealStatusText(statusText: String?) -> (attr: NSMutableAttributedString?, linkMatchResults: [HMMatchResult]?) {}
  • addHighLightedAttr 方法添加返回值 [HMMatchResult]
/// 添加高亮属性
///
/// - parameter attributedString:
private func addHighLightAttr(attributedString: NSMutableAttributedString) -> [HMMatchResult] {

    // 定义匹配结果数组
    var matchResult = [HMMatchResult]()


    // 匹配话题
    (attributedString.string as NSString).enumerateStringsMatchedByRegex("#[^#]+#") { (captureCount, caputureString, captureRange, stop) -> Void in
        attributedString.addAttribute(NSForegroundColorAttributeName, value: RGB(r: 80, g: 125, b: 175), range: captureRange.memory)
        // 保存匹配结果
        let result = HMMatchResult(captureString: caputureString.memory! as String, captureRange: captureRange.memory)
        matchResult.append(result)

    }
    ...
    // 返回结果
    return matchResult
}
  • 更改 dealStatusText 方法处理结果
let resultResult = dealStatusText(retweetText)
retweetStatusAttributedString = resultResult.attr
retweetMatchResults = resultResult.linkMatchResults
...
let originalResult = dealStatusText(status.text)
originalStatusAttributedString = originalResult.attr
originalMatchResults = originalResult.linkMatchResults
  • HMStatusLabel 添加特殊字符匹配结果的属性
var linkMatchResult: [HMMatchResult]?
  • 在给原创微博View(转发微博)设置数据的时候给 HMStatusLabel 设置 linkMatchResult 属性
contentLabel.linkMatchResult = statusViewModel?.originalMatchResults
  • HMStatusLabeltouchesBegan 方法里面判断当前用户点击的点是在哪一个 result 范围内
// 查看在哪一个 result 范围之内
for value in linkMatchResult! {
    // 当前用户点击的位置是特殊字符的位置
    if NSLocationInRange(range.location, value.captureRange) {
        print(value.captureString)

    }
}
  • 设置 textView 当前选中的范围
textView.selectedRange = value.captureRange
// 根据 textView 取到当前用户点击的范围并添加 View
let rects = textView.selectionRectsForRange(textView.selectedTextRange!)
for rect in rects {
    let r = rect as! UITextSelectionRect
    let view = UIView(frame: r.rect)
    view.layer.cornerRadius = 5
    view.layer.masksToBounds = true
    view.backgroundColor = RGB(r: 177, g: 215, b: 255)
    self.insertSubview(view, atIndex: 0)
}

运行测试

  • 利用数组保存上一步添加的 view
lazy var linkSubView: [UIView] = [UIView]()
...
for rect in rects {
    ...
    self.insertSubview(view, atIndex: 0)
    linkSubView.append(view)
}
  • touchesEnded 方法里面遍历数组将控件从父控件中移除并清空集合
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    for value in linkSubView {
        value.removeFromSuperview()
    }
    linkSubView.removeAll()
}
  • 重写 touchesCancelled ,并在内部调用 touchesEnded 方法
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
    touchesEnded(touches!, withEvent: event)
}

运行测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,573评论 18 139
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,005评论 4 61
  • 2017-2-3晴 今晚夜宿束河古镇。只有大床房了,我们三人只好挤在一张床上了。 我们很久没有在一张床上睡觉了,都...
    心境色彩阅读 260评论 0 0
  • 重新梳理一下第一章导图
    JessicaPeng阅读 155评论 0 1