图文混排之控件使用

图文混排在iOS开发中经常遇到, 故总结了多种解决方案, 以便将来使用。本文先总结简单的方法-对控件的使用。这些控件包括UIWebView, UILabel, UITextView, UITextField, 都可以进行图文混排, 各有各的使用场景。

下图是控件基础架构, iOS7以前这几个控件都是基于WebKit开发, 而iOS7之后推出了TextKit, 重写了TextView, Label, TextField这几个控件。

一. UIWebView

WebView呈现图文混排比较简单, 只需要加载写好的html或者URL, 图文混排由网页实现。

二. UILabel

不管是UILabel还是UITextView的图文混排, 都是操作NSAttributedString, NSMutableAttributedString。

  • 我们先创建一个label, 让label能自动换行, 居中显示
// 创建label
let label = UILabel(frame: self.view.bounds)
label.textAlignment = .Center   // 居中排列
label.lineBreakMode = .ByWordWrapping    // 按词换行
label.numberOfLines = 0 // 自动换行
self.view.addSubview(label)
  • 创建NSMutableAttributedString
let attributedText = NSMutableAttributedString(string: "Jacob was a year and a half older than I and seemed to enjoy reading my gestures and translating my needs to adults. He ensured that cartoons were viewed, cereal was served, and that all bubbles were stirred out of any remotely bubbly beverage intended for me. In our one-bedroom apartment in southern New Jersey, we didn’t have many toys. But I had a big brother and Jacob had a baby sister. We were ignorant of all the pressed plastic playthings we didn’t have.")
  • 文本有了之后, 就只剩下图片了。可是NSMutableString并不直接支持图片加入其中, 但是能插入附件, 我们把图片当附件插入文本中
// 图片附件
let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(named: "catanddog") // 设置附件的image属性

// 调整图片位置到中间
imageAttachment.bounds = CGRectMake(0, -imageAttachment.image!.size.height / 2, imageAttachment.image!.size.width, imageAttachment.image!.size.height)

// 将带图片附件的string插入到指定位置
attributedText.insertAttributedString(NSAttributedString(attachment: imageAttachment), atIndex: 50)

图文混排的效果就出现了

至于怎么使用网络图片, 其实很简单, 只需要在图片下载完之后插入到指定位置就可以了。

  • 觉得文本样式太简单了, 我们可以多点样式
// 高亮
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, 3))

// 下划线
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, 10))

// 字体
attributedText.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(50), range: NSMakeRange(20, 10))

// 背景色
attributedText.addAttribute(NSBackgroundColorAttributeName, value: UIColor.yellowColor(), range: NSMakeRange(30, 10))

// 删除线
attributedText.addAttribute(NSStrikethroughStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(120, 20))
   
// 斜体
attributedText.addAttribute(NSObliquenessAttributeName, value: 1, range: NSMakeRange(100, 10))
   
// 阴影
let shadow = NSShadow()
shadow.shadowOffset = CGSize(width: 3.0, height: 3.0)
shadow.shadowColor = UIColor.redColor()
attributedText.addAttribute(NSShadowAttributeName, value: shadow, range: NSMakeRange(0, 15))
   
// 横竖文本
attributedText.addAttribute(NSVerticalGlyphFormAttributeName, value: 0, range: NSMakeRange(70, 10))

丰富多样的效果出现了


至此, Label的图文混合(包括富文本)处理已经完成, 至于排版视具体情况而定。

但是, Label的富文本处理出现了几个问题, 现一并记录在此

  • 1.只剩下下面两个样式下划线和阴影, 两个样式的范围索引都从0开始
// 下划线
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, 10))

// 阴影
let shadow = NSShadow()
shadow.shadowOffset = CGSize(width: 3.0, height: 3.0)
shadow.shadowColor = UIColor.redColor()
attributedText.addAttribute(NSShadowAttributeName, value: shadow, range: NSMakeRange(0, 15))

出现了以下效果


  • 2.还是只有下划线和阴影两个样式, 下划线的范围索引从0开始, 阴影的范围索引只要是非0就可以
// 下划线
attributedText.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, 10))

// 阴影
let shadow = NSShadow()
shadow.shadowOffset = CGSize(width: 3.0, height: 3.0)
shadow.shadowColor = UIColor.redColor()
attributedText.addAttribute(NSShadowAttributeName, value: shadow, range: NSMakeRange(100, 15))

结果阴影效果未出现


这两个问题都是同一个原因导致, 只要将下划线的范围索引改成非0就都可以正常显示, 这里有相同问题的讨论, 可供参考。

三. UITextView

  • 1.TextView的富文本处理跟Label一样都是操作NSAttributedString, 所以把Label的富文本代码复制过来, 就一样可以显示效果了



    而针对Label出现的两个问题在TextView上不会出现, 说明Label的富文本是有问题的。

  • 2.TextView富文本的自定义方式, 先来看下这种方式下的结构(图片来源于Apple)

  • TextStorage 文本存储类, 可继承实现自定义功能
  • LayoutManager 文本排版类, 通过将TextStorage中的的数据转换为显示的文本
  • TextContainer 文本容器, 定义了文本可显示的区域, 通过子类化NSTextContainer来创建别的一些形状,例如圆形、不规则的形状。

这三个类之间可以形成一对多的关系, 如图所示(图来自苹果)


此处我们只创建一个一对一的关系

// 创建TextStorage, TextStorage必须被强引用
self.textStorage = NSTextStorage(string: text)

// 创建LayoutManager
let layoutManager = NSLayoutManager()
self.textStorage?.addLayoutManager(layoutManager)
 
// 创建TextContainer   
let textContainer = NSTextContainer(size: self.view.bounds.size)
layoutManager.addTextContainer(textContainer)
   
// 创建TextView, 传入textContainer   
self.textView = UITextView(frame: self.view.bounds, textContainer: textContainer)
self.textView?.delegate = self
self.view.addSubview(self.textView!)

这样就创建好了, 而例如高亮, 阴影等样式可以像之前那样直接设置, 此处不多说了。然后创建图片视图

self.imageView = UIImageView(image: UIImage(named: "catanddog"))
self.imageView?.center = CGPointMake(self.view.bounds.size.width / 2, self.imageView!.frame.size.height / 2 + 200)
self.view.addSubview(self.imageView!)

// 并给imageView加入手势, 为了拖动图片
self.imageView?.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(imagePan)))
self.imageView?.userInteractionEnabled = true

界面都创建好了, 那么就来更新图片在文本中的位置

func updateExclusionPaths() {
    // 计算图片所占范围
    var imageRect = self.textView?.convertRect(self.imageView!.frame, fromView: self.view)
    imageRect!.origin.x -= self.textView!.textContainerInset.left;
    imageRect!.origin.y -= self.textView!.textContainerInset.top;
    let path = UIBezierPath(rect: imageRect!)
    self.textView?.textContainer.exclusionPaths = [path]
}

这样就出现了图文混排我们所需要的效果了, 既然我们加入了拖动图片的手势, 那么怎么让文本图片拖动而变化了

func imagePan(pan: UIPanGestureRecognizer) {
    if pan.state == .Began {
      self.panOffset = pan.locationInView(self.imageView!)
    }
       
    let location = pan.locationInView(self.view)
    var imageCenter = self.imageView!.center
       
    // 让图片随手势变化位置   
    imageCenter.x = location.x - self.panOffset.x + self.imageView!.frame.size.width / 2
   imageCenter.y = location.y - self.panOffset.y + self.imageView!.frame.size.height / 2
        
   self.imageView?.center = imageCenter
   self.imageCenterY = imageCenter.y + self.textView!.contentOffset.y + navigationBarHeight
      
   // 更新文本排版  
   updateExclusionPaths()
}

此时, 拖动图片, 文本也可随着图片位置的变化而重新排版了, perfect!!!有没有? 但是, 如果文本太长导致textView可以上下滚动了, 完了, 图片不动了

// 实现ScrollViewDelegate协议, 让textView滚动的时候, 图片也动起来
func scrollViewDidScroll(scrollView: UIScrollView) {
    self.imageView?.center = CGPointMake(self.imageView!.center.x, self.imageCenterY - (scrollView.contentOffset.y + navigationBarHeight))
}

perfect!!! 我们来看看效果

![](http://oc3j5gzq3.bkt.clouddn.com/2016-08-26-2016-08-26 11.55.22.gif)

可是还是有一个问题, 当设置了字体样式时就出问题了

// 字体
self.textStorage?.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(50), range: NSMakeRange(220, 1))

设置字体后, 当拖动图片到这个大字体附近就会出现大段空白的情况, 谁能告诉我为什么? 有大牛能帮解释下么?

好了, 这篇到此结束了, 源码在此, 请查收!!!

参考:
https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html
https://objccn.io/issue-5-1/
https://www.raywenderlich.com/50151/text-kit-tutorial

本文由啸寒原创, 转载请注明出处!!!

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,952评论 4 60
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,128评论 25 707
  • 昨晚年会告一段路,节目很精彩,气氛很嗨,人很累。 肚子有点饿,买了薯片啤酒回家,准备好好地看部好电影。选了《海街日...
    小鞋子碎碎念阅读 3,452评论 3 3
  • 朋友们大家好 今天很幸运,销售一瓶化妆品,又收到一个快递。 可是聊来一个满腹牢骚的人,又让人心里不舒服,这种人到底...
    笑看风云_9628阅读 228评论 0 5
  • 包管理器的兴起 windows世界安装软件,一般都是在网上或者某个地方找到一个安装包,下载运行后,按照图形界面一步...
    灭蒙鸟阅读 2,544评论 2 4