[Swift]iOS开发:在视图上绘制文字的3种高效方法以及如何自适应文本高度

来源:互联网 发布:阿里云备案电话 编辑:程序博客网 时间:2024/05/18 07:27

在开始之前我想先着重介绍下 NSAttributedString

NSAttributedString叫做富文本,是一种带有属性的字符串,通过它可以轻松的在一个字符串中表现出多种字体、字号、字体大小等各不相同的风格,还可以对段落进行格式化。

通过 NSAttributedString 我们可以很方便的实现文字排版,得到我们想要的文字效果。具体的属性介绍参考:iOS之文本属性Attributes的使用

接下来我简单介绍下使用方法顺便附上 UILabel 自适应高度的正确写法:

    override func viewDidLoad() {        super.viewDidLoad()        let string = "要显示的长长长长长长长长长长长文字"        let paragraph = NSMutableParagraphStyle()//设置文本的段落样式        paragraph.lineBreakMode = .byCharWrapping//换行方式        paragraph.lineSpacing = 10//行距        let font = UIFont.systemFont(ofSize: 15)//设置字体大小        let color = UIColor.yellow//设置字体颜色        let dict:[String:Any] = [            NSFontAttributeName:font,            NSForegroundColorAttributeName:color,            NSParagraphStyleAttributeName:paragraph        ]//属性键值对,这里的属性名称可在我上边给出的文章里找到,一般是以NS开头        let stringSize = string.boundingRect(with: CGSize(width:100,height:CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: dict, context: nil).size//获取字段显示区域的大小(给定宽100,高度无上限)        let attribute = NSAttributedString(string: string, attributes: dict)        let label = UILabel(frame: CGRect(origin: CGPoint(x:100,y:100), size: stringSize))//这里UILabel的大小一定是上边算好的字段大小        label.backgroundColor = UIColor.brown        label.attributedText = attribute//设置UILabel的文本属性        label.numberOfLines = 0//设置多行,必须        view.addSubview(label)    }

显示效果:
这里写图片描述

NSAttributedString结合boundingRect能够完美解决控件自适应文本高度,而且完全不需要自己去算显示区域的大小,方便快捷,推荐使用。


以下正文:

第一种方法:重写 View 的 drawRect 方法,自己画文字

class TextView: UIView {    private var _text:String!    init(frame: CGRect,text:String) {        super.init(frame: frame)        _text = text    }    override func draw(_ rect: CGRect) {        _text.draw(in: rect, withAttributes: [NSForegroundColorAttributeName:UIColor.black,NSFontAttributeName:UIFont.systemFont(ofSize: 14)])//给定文字绘制区域为视图大小    }    required init?(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }}////////////////////////////////////////    override func viewDidLoad() {        super.viewDidLoad()        let textView = TextView(frame: CGRect(x:100,y:100,width:100,height:100), text: "测试文字测试文字测试文字测试文字测试文字")        textView.backgroundColor = UIColor.brown        view.addSubview(textView)    }

这里写图片描述

这个方法有很多妙用,比如 iOS 的 UITextView 没有占位符,那么我们就可以重写 UITextView 的 drawRect 方法,自己绘制占位符。具体可参考我的另一篇文章给 UITextView 添加占位符的方法

第二种方法:使用CATextLayer

    override func viewDidLoad() {        super.viewDidLoad()        let string = "要显示的长长长长长长长长长长长长长长长长文本"        let stringSize = string.boundingRect(with: CGSize(width:100,height:CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 14)], context: nil).size        let textLayer = CATextLayer()        textLayer.frame = CGRect(x: 100, y: 100, width: stringSize.width, height: stringSize.height*1.03)//这里高度有大约0.03的偏差        textLayer.string = NSAttributedString(string: string, attributes: [NSForegroundColorAttributeName : UIColor.black,NSFontAttributeName:UIFont.systemFont(ofSize: 14)])        textLayer.backgroundColor = UIColor.brown.cgColor        textLayer.isWrapped = true//设置是否自动换行        textLayer.contentsScale = UIScreen.main.scale//寄宿图的像素尺寸和视图大小的比例,不设置为屏幕比例文字就会像素化        view.layer.addSublayer(textLayer)    }

这里写图片描述
这里要注意的是计算出的高度和CATextLayer实际显示的高度有偏差,原因不详。

第三种方法:用 CoreText 进行文本布局,因为 CoreText 是基于 CoreGraphics 的,所以需要获取当前的上下文

class TextView:UIView{    override init(frame: CGRect) {        super.init(frame: frame)    }    //一旦你重写drawRect方法,系统会自动创建一个上下文    override func draw(_ rect: CGRect) {        super.draw(rect)        let ctx = UIGraphicsGetCurrentContext()//获取当前上下文        let size = "我是长长长长长长长长长长长长长长长文字".boundingRect(with: CGSize(width:bounds.width,height:CGFloat(MAXFLOAT)), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 18)], context: nil).size        //计算文本显示区域大小        ctx?.translateBy(x: 0, y: size.height)        ctx?.scaleBy(x: 1, y: -1)        //翻转坐标系,iOS 底层绘制引擎的原点都在左下角        ctx?.setFillColor(UIColor.brown.cgColor)        ctx?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))//设置显示区域的颜色        let attrstr = NSAttributedString(string: "我是长长长长长长长长长长长长长长长文字", attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 18),NSForegroundColorAttributeName:UIColor.yellow])        let frameSetter = CTFramesetterCreateWithAttributedString(attrstr)//根据NSAttributedString生成CTFramesetter        let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: size.width, height: size.height)).cgPath//设置文本显示区域大小        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attrstr.length), path, nil)//根据以上两个参数以及文本宽度获取CTFrame        CTFrameDraw(frame, ctx!)//绘制文本    }    required init?(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }}////////////////////////////////////////    override func viewDidLoad() {        super.viewDidLoad()        let textView = TextView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))        view.addSubview(textView)    }

这里写图片描述
另外,无论我们想要绘制什么内容,首要任务就是先获取上下文,你可以把上下文理解成一块画布,有了画布才能画东西;iOS 有三种获取上下文的方式:重写 UIView 的drawRect 和 drawRect inContext 方法会自动生成一个上下文,我们直接在这两个方法里绘制内容,UIView 就能自动渲染出来了,还有一种方法是使用UIGraphicsBeginImageContextWithOptions方法自己创建一个 UIImage 类型的上下文

        UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.main.scale)//创建一个基于位图的上下文(context),并将其设置为当前上下文(context),参数:size位图大小,opaque是否为不透明(设置为true能提升性能,scale缩放因子)        //这里复制上段代码就行了        let img = UIGraphicsGetImageFromCurrentImageContext()//生成UIImage        UIGraphicsEndImageContext()//使用完后一定要记得释放        let imgview = UIImageView(frame: rect)        imgview.image = img        view.addSubview(imgview)

这种方法应该是三种方法里最高效的,也是最复杂了,想具体深入研究可参考【iOS】使用CoreText实现图文混排

如果你还有其他方法请一定告诉我

阅读全文
1 0
原创粉丝点击