当前位置: > > > Swift - 文本框textView图文混排的实现(附样例)

Swift - 文本框textView图文混排的实现(附样例)

(本文代码已升级至Swift3)

我们使用文本框(UITextView)时,除了输入文字外,可能还会想在里面插入一些图片。或者有一些图文混排的内容需要展示出来。 这个只需要通过 textView 的属性化文本即可实现。j将图片以附件的形式插入即可。
本文通过样例演示如何实现 textView 的图文混排,同时还可以选择插入图片的模式,是保持原图大小,还是自适应尺寸(这些可以混合使用的。)

1,效果图
(1)不改变插入图片的大小

(2)让图片与行高保持一致。这样图片就不会撑大行高,同时会与文字的大小保持一致。适合用来插入表情图标。
            

(3)让图片占满一行。适合普通图片或者大图的插入。


2,样例代码
import UIKit

class ViewController: UIViewController {
    
    //图文混排显示的文本区域
    @IBOutlet weak var textView: UITextView!
    
    //文字大小
    let textViewFont = UIFont.systemFont(ofSize: 22)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //初始化显示默认内容
        insertString("欢迎欢迎!")
        insertPicture(UIImage(named: "icon")!, mode:.fitTextLine)
        insertString("\n欢迎访问:")
        insertPicture(UIImage(named: "logo")!)
        insertPicture(UIImage(named: "bg")!, mode:.fitTextView)
    }
    
    //插入文字
    func insertString(_ text:String) {
        //获取textView的所有文本,转成可变的文本
        let mutableStr = NSMutableAttributedString(attributedString: textView.attributedText)
        //获得目前光标的位置
        let selectedRange = textView.selectedRange
        //插入文字
        let attStr = NSAttributedString(string: text)
        mutableStr.insert(attStr, at: selectedRange.location)
        
        //设置可变文本的字体属性
        mutableStr.addAttribute(NSFontAttributeName, value: textViewFont,
                                range: NSMakeRange(0,mutableStr.length))
        //再次记住新的光标的位置
        let newSelectedRange = NSMakeRange(selectedRange.location + attStr.length, 0)
        
        //重新给文本赋值
        textView.attributedText = mutableStr
        //恢复光标的位置(上面一句代码执行之后,光标会移到最后面)
        textView.selectedRange = newSelectedRange
    }
    
    //插入图片
    func insertPicture(_ image:UIImage, mode:ImageAttachmentMode = .default){
        //获取textView的所有文本,转成可变的文本
        let mutableStr = NSMutableAttributedString(attributedString: textView.attributedText)
        
        //创建图片附件
        let imgAttachment = NSTextAttachment(data: nil, ofType: nil)
        var imgAttachmentString: NSAttributedString
        imgAttachment.image = image
        
        //设置图片显示方式
        if mode == .fitTextLine {
            //与文字一样大小
            imgAttachment.bounds = CGRect(x: 0, y: -4, width: textView.font!.lineHeight,
                                          height: textView.font!.lineHeight)
        } else if mode == .fitTextView {
            //撑满一行
            let imageWidth = textView.frame.width - 10
            let imageHeight = image.size.height/image.size.width*imageWidth
            imgAttachment.bounds = CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight)
        }
        
        imgAttachmentString = NSAttributedString(attachment: imgAttachment)
        
        //获得目前光标的位置
        let selectedRange = textView.selectedRange
        //插入文字
        mutableStr.insert(imgAttachmentString, at: selectedRange.location)
        //设置可变文本的字体属性
        mutableStr.addAttribute(NSFontAttributeName, value: textViewFont,
                                range: NSMakeRange(0,mutableStr.length))
        //再次记住新的光标的位置
        let newSelectedRange = NSMakeRange(selectedRange.location+1, 0)
        
        //重新给文本赋值
        textView.attributedText = mutableStr
        //恢复光标的位置(上面一句代码执行之后,光标会移到最后面)
        textView.selectedRange = newSelectedRange
        //移动滚动条(确保光标在可视区域内)
        self.textView.scrollRangeToVisible(newSelectedRange)
    }
    
    //插入图片1:保持原始尺寸
    @IBAction func btnClick1(_ sender: AnyObject) {
        insertPicture(UIImage(named: "logo")!)
    }
    
    //插入图片2:适应行高
    @IBAction func btnClick2(_ sender: AnyObject) {
        insertPicture(UIImage(named: "icon")!, mode:.fitTextLine)
    }
    
    //插入图片3:适应textView宽度
    @IBAction func btnClick3(_ sender: AnyObject) {
        insertPicture(UIImage(named: "bg")!, mode:.fitTextView)
    }
    
    //插入文字
    @IBAction func btnClick4(_ sender: AnyObject) {
        insertString("hangge.com")
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

//插入的图片附件的尺寸样式
enum ImageAttachmentMode {
    case `default`  //默认(不改变大小)
    case fitTextLine  //使尺寸适应行高
    case fitTextView  //使尺寸适应textView
}
源码下载hangge_1213.zip
评论8
  • 8楼
    2018-07-03 18:30
    Kimi

    航哥,不好意思,我找到自己的問題在哪裡了,我用了NSMutableAttributeString去append另一個NSMutableAttributeString,所以卡卡的,雖然不知道原理是怎樣,但問題解決了,不好意思麻煩您了

    站长回复

    原来是这样,解决了就好。

  • 7楼
    2018-07-03 15:00
    Kimi

    航哥,我又來詢問您問題了,我發現我在textView編輯文字與插入圖片後,之後中文的輸入都會卡卡頓頓的,尤其是插入由相機得到的圖片時,變得不是很流暢,想詢問您之前有沒有處理過類似的問題

    站长回复

    这个我之前倒没遇到过。

  • 6楼
    2017-12-08 10:43
    kang

    航哥,你说客户端这边把内容中图片的部分替换成相应的占位标记,但是标记不是显示到 textView 上边了吗

    站长回复

    数据可以在内存里分两份,一份是用于textView显示的,另一份用来做替换并上传的。

  • 5楼
    2017-07-28 16:20
    布袋

    航哥, 能补充下服务器是获得textView的内容吗?
    还有,获取后ios端又是如何显示的呢?

    站长回复

    1,内容提交
    比如你要上传一个图文混排的内容,我通常做法是先把内容中的图片提取出来上传到服务器,服务器保存图片后返回图片id,比如:101,102
    然后客户端这边把内容中图片的部分替换成相应的占位标记,比如:欢迎来到[img:101],你好[img:102]。接着把这个处理后的文本发送到服务端存储起来。

    2,内容显示
    比如iOS得到的图文混排的内容是这样:欢迎来到[img:101],你好[img:102]
    然后解析出里面的图片占位标记,根据id把对应的图片从服务器上下载下来(服务端提供个根据id获取图片的接口)。
    最后把图片插入到文字相应的位置显示即可。

  • 4楼
    2017-03-22 17:25
    敬冬

    原来如此!谢谢大神

    站长回复

    不客气。

  • 3楼
    2017-03-21 17:10
    敬冬

    我是Alamofire get服务器的图文内容,通过for循环把服务器的图文信息逐数据逐行根据类型不同,分别调用 insertString 和 insertPicture 插入到textview,然后运行结果就成了图片在一起,文字在一起,服务器的数据是一段文字一张图片。

    站长回复

    我看了下你的代码。发现虽然内容是按顺序逐条解析,但图片是使用Alamofire异步获取的,也就是说要等图片下载下来才会执行插入操作。而这之前文字肯定都已经插入完毕了。

  • 2楼
    2017-03-17 17:16
    敬冬

    为什么我在写了一个for循环每次加载一段文字和一张图片,最后显示的时候文字却跑到一段里,图片全集中显示在底部?

    站长回复

    我又测试了下我上传的工程项目,是没问题的啊?

  • 1楼
    2016-08-15 10:40
    Kimi

    感謝航歌的分享,已經兩次在你這邊得到答案了,真的很感謝你

    站长回复

    不客气,能帮助到你我也很高兴。欢迎常来看看,我会继续创作更多的文章分享给大家。