iOS Swift 3.0 富文本

todo

  • [ x ] 自己改造了富文本的属性设置
  • [ ] 代码中用到的API后续补上

富文本

存在文字的控件或者组建都可以设置富文本
UILabel, UIButton, UITextView 都可以设置富文本
计算文本Size

Code

import UIKit

enum TextAttributes {
    /// 字体
    case font(value: UIFont) // UIFont, default Helvetica(Neue) 12
    
    /// 设置段落属性
    case paragraphStyle(value: NSParagraphStyle) // NSParagraphStyle, default defaultParagraphStyle
    
    /// 设置前景颜色
    case foregroundColor(value: UIColor) // UIColor, default blackColor
    
    /// 设置文本背景颜色
    case backgroundColor(value: UIColor) // UIColor, default nil: no background
    
    /// 设置连体属性,取值为NSNumber对象(整数),1表示使用默认的连体字符,0表示不使用,2表示使用所有连体符号(iOS不支持2)。而且并非所有的字符之间都有组合符合。如 fly ,f和l会连起来。
    case ligatures(value: Int) // NSNumber containing integer, default 1: default ligatures, 0: no ligatures
    
    /// 设置字距 ,负值间距变窄,正值间距变宽
    case kern(value: CFloat) // NSNumber containing floating point value, in points; amount to modify default kerning. 0 means kerning is disabled.
    
    /// 设置删除线
    case strikethroughStyle(value: NSUnderlineStyle) // NSNumber containing integer, default 0: no strikethrough
    
    /// 设置下划线样式
    case underlineStyle(value: NSUnderlineStyle) // NSNumber containing integer, default 0: no underline
    
    /// 设置笔刷颜色 设置填充部分颜色 设置中间部分颜色可以使用 NSForegroundColorAttributeName 属性来进行
    case strokeColor(value: UIColor) // UIColor, default nil: same as foreground color
    
    /// 设置笔画的宽度,负值填充效果,正值是中空效果
    case strokeWidth(value: CGFloat) // NSNumber containing floating point value, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0)
    
    /// 设置阴影
    case shadow(value: NSShadow) // NSShadow, default nil: no shadow
    
    /// 设置文本特殊效果,目前只有一个可用效果  NSTextEffectLetterpressStyle(凸版印刷效果)
    case textEffect(value: String) // NSString, default nil: no text effect NSTextEffectLetterpressStyle
    
    /// 设置文本附件,常用于文字的图文混排
    case attachment(value: NSTextAttachment) // NSTextAttachment, default nil
    
    case linkString(value: String) // NSURL (preferred) or NSString
    case linkURL(value: URL) // NSURL (preferred) or NSString
    
    /// 设置基线偏移值 正值上偏,负值下偏
    case baselineOffset(value: CGFloat) // NSNumber containing floating point value, in points; offset from baseline, default 0
    
    /// 设置下划线颜色
    case underlineColor(value: UIColor) // UIColor, default nil: same as foreground color
    
    /// 设置删除线颜色
    case strikethroughColor(value: UIColor) // UIColor, default nil: same as foreground color
    
    /// 设置字体倾斜度,正值右倾,负值左倾
    case obliqueness(value: CGFloat) // NSNumber containing floating point value; skew to be applied to glyphs, default 0: no skew
    
    /// 设置字体的横向拉伸,取值为NSNumber (float),正值拉伸 ,负值压缩
    case expansion(value: CGFloat) // NSNumber containing floating point value; log of expansion factor to be applied to glyphs, default 0: no expansion
    
    /// 设置文字的书写方向
    case writingDirection(value: [NSWritingDirection]) // NSArray of NSNumbers representing the nested levels of writing direction overrides as defined by Unicode LRE, RLE, LRO, and RLO characters.  The control characters can be obtained by masking NSWritingDirection and NSWritingDirectionFormatType values.  LRE: NSWritingDirectionLeftToRight|NSWritingDirectionEmbedding, RLE: NSWritingDirectionRightToLeft|NSWritingDirectionEmbedding, LRO: NSWritingDirectionLeftToRight|NSWritingDirectionOverride, RLO: NSWritingDirectionRightToLeft|NSWritingDirectionOverride,
    
    /// 设置文字排版方向,0表示横排文本,1表示竖排文本  在iOS中只支持0
    case verticalGlyphForm(value: Int) // An NSNumber containing an integer value.  0 means horizontal text.  1 indicates vertical text.  If not specified, it could follow higher-level vertical orientation settings.  Currently on iOS, it's always horizontal.  The behavior for any other value is undefined.
}

fileprivate func configureAttributes(attributes: [TextAttributes]?)->[String: Any]? {
    guard let attributes = attributes else {
        return nil
    }
    
    var temp:[String: Any] = [String: Any]()
    for attribute in attributes {
        switch attribute {
        case let .font(value):
            temp[NSFontAttributeName] = value
        case let .paragraphStyle(value):
            temp[NSParagraphStyleAttributeName] = value
        case let .foregroundColor(value):
            temp[NSForegroundColorAttributeName] = value
        case let .backgroundColor(value):
            temp[NSBackgroundColorAttributeName] = value
        case let .ligatures(value):
            temp[NSLigatureAttributeName] = value
        case let .kern(value):
            temp[NSKernAttributeName] = value
        case let .strikethroughStyle(value):
            temp[NSStrikethroughStyleAttributeName] = value
        case let .underlineStyle(value):
            temp[NSUnderlineStyleAttributeName] = value
        case let .strokeColor(value):
            temp[NSStrokeColorAttributeName] = value
        case let .strokeWidth(value):
            temp[NSStrokeWidthAttributeName] = value
        case let .shadow(value):
            temp[NSShadowAttributeName] = value
        case .textEffect(_):
            temp[NSTextEffectAttributeName] = NSTextEffectLetterpressStyle
        case let .attachment(value):
            temp[NSAttachmentAttributeName] = value
        case let .linkString(value):
            temp[NSLinkAttributeName] = value
        case let .linkURL(value):
            temp[NSLinkAttributeName] = value
        case let .baselineOffset(value):
            temp[NSBaselineOffsetAttributeName] = value
        case let .underlineColor(value):
            temp[NSUnderlineColorAttributeName] = value
        case let .strikethroughColor(value):
            temp[NSStrikethroughColorAttributeName] = value
        case let .obliqueness(value):
            temp[NSObliquenessAttributeName] = value
        case let .expansion(value):
            temp[NSExpansionAttributeName] = value
        case let .writingDirection(value):
            temp[NSWritingDirectionAttributeName] = value
        case let .verticalGlyphForm(value):
            temp[NSVerticalGlyphFormAttributeName] = value
        }
    }
    return temp
}

extension String {
    /// 根据文字属性计算size
    ///
    /// - Parameters:
    ///   - size: 限定size
    ///   - attributes: 属性
    /// - Returns: 返回新的size
    fileprivate func getTextSize(string: String,size: CGSize,attributes: [TextAttributes]?)-> CGRect {
        let attribute = configureAttributes(attributes: attributes)
        let text = NSString(string: string)
        let newSize =  text.boundingRect(with: size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: attribute, context: nil)
        return newSize
    }
    
    /// 根据文字限定的宽度来计算所占高度
    ///
    /// - Parameters:
    ///   - width: 宽度
    ///   - attributes: 属性
    /// - Returns: 返回的高度
    func getTextHeight(width: CGFloat,attributes: [TextAttributes]?) -> CGFloat {
        let size = CGSize(width: width, height: 0)
        let rect = getTextSize(string: self, size: size, attributes: attributes)
        return rect.height
    }
    
    /// 根据文字限定的高度来计算所占宽度
    ///
    /// - Parameters:
    ///   - height: 高度
    ///   - attributes: 属性
    /// - Returns: 返回的宽度
    func getTextWidth(height: CGFloat,attributes: [TextAttributes]?) -> CGFloat {
        let size = CGSize(width: 0, height: height)
        let rect = getTextSize(string: self, size: size, attributes: attributes)
        return rect.width
    }
    
    /// 转换成不可变富文本
    ///
    /// - Parameter attributes: 富文本属性
    /// - Returns: 不可变富文本
    func conversion(attributes: [TextAttributes]?) -> NSAttributedString {
        let attribute = configureAttributes(attributes: attributes)
        let attributedString =  NSAttributedString(string: self, attributes: attribute)
        return attributedString
    }
    
    /// 转换成可变富文本
    ///
    /// - Parameter attributes: 富文本属性
    /// - Returns: 可变富文本
    func conversionM(attributes: [TextAttributes]?) -> NSMutableAttributedString {
        let attribute = configureAttributes(attributes: attributes)
        let mutableAttributedString = NSMutableAttributedString(string: self, attributes: attribute)
        return mutableAttributedString
    }
    
    /// 图文混排
    ///
    /// - Parameters:
    ///   - image: 图标
    ///   - bounds: 位置 (0,0,20,20)
    ///   - string: 文字
    ///   - textAttributes: 文字属性
    /// - Returns: 返回一个可变富文本
    func imageWithText(image: UIImage, bounds:CGRect, string: String ,textAttributes: [TextAttributes]?) -> NSMutableAttributedString {
        let attachment = NSTextAttachment()
        attachment.image = image
        attachment.bounds = bounds
        let mutableAttributedString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: attachment))
        let text:NSAttributedString = string.conversion(attributes: textAttributes)
        mutableAttributedString.append(text)
        return mutableAttributedString
    }
    
    /// 包含字串
    ///
    /// - Parameters:
    ///   - pattern: 正则表达式
    /// - Returns: true yes
    func containsMatch(pattern: String) -> Bool {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSMakeRange(0, self.characters.count)
            return (regex.firstMatch(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: range) != nil)
            
        } catch _ {
            fatalError( "rrror")
        }
        
        
    }
    
    /// 高亮
    ///
    /// - Parameters:
    ///   - pattern: 正则表达式
    ///   - attributes: 匹配到的字符属性
    /// - Returns: 返回新串
    func highlightMatches(pattern: String, attributes: [TextAttributes]?) -> NSAttributedString {
        
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSMakeRange(0, self.characters.count)
            let matches = regex.matches(in: self, options: [], range: range)
            let attributedText = NSMutableAttributedString(string: self)
            let attribute = configureAttributes(attributes: attributes) ?? [:]
            for match in matches {
                attributedText.addAttributes(attribute, range: match.range)
            }
            return attributedText.copy() as! NSAttributedString
        } catch _ {
            fatalError( "字符串匹配错误")
        }
    }
    
    
    /// 匹配到的list
    ///
    /// - Parameters:
    ///   - pattern: 正则表达式
    /// - Returns: 返回匹配到的列表
    func listMatches(pattern: String) -> [String] {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSMakeRange(0, self.characters.count)
            
            let matches = regex.matches(in: self, options: [], range: range)
            
            return matches.map{
                let range = $0.range
                return NSString(string: self).substring(with: range)
            }
        } catch _ {
            fatalError( "字符串匹配错误")
        }
    }
    
    
    func listGroups(pattern: String) -> [String] {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSMakeRange(0, self.characters.count)
            let matches = regex.matches(in: self, options: [], range: range)
            
        
            var groupMatches = [String]()
            
            for match in matches {
                let rangeCount = match.numberOfRanges
                for group in 0..<rangeCount {
                    let temp = NSString(string: self).substring(with: match.rangeAt(group))
                    groupMatches.append(temp)
                }
            }
            return groupMatches
        } catch _ {
            fatalError( "字符串匹配错误")
        }
    }
    
    /// 替换字符
    ///
    /// - Parameters:
    ///   - pattern: 正则表达式
    ///   - replacementString: 替换的字符
    /// - Returns: 返回被替换后的字符串
    func replaceMatches(pattern: String, withString replacementString: String) -> String?  {
        do {
            let regex = try NSRegularExpression(pattern: pattern, options: [])
            let range = NSMakeRange(0, self.characters.count)
            
            return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacementString)
            
        } catch _ {
            fatalError("字符串匹配错误")
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容