【问题标题】:How to draw text in trapezoid (or circle)?如何以梯形(或圆形)绘制文本?
【发布时间】:2017-07-24 21:30:35
【问题描述】:

假设我有一些文本:“梯形中的一些属性文本。

我有 NSAttributedString 扩展,它返回带有属性文本的 UIImage:

extension NSAttributedString {
    func asImage() -> UIImage? {
        defer {
            UIGraphicsEndImageContext()
        }
        let size = boundingRect(with: CGSize.zero, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil).size
        UIGraphicsBeginImageContext(size)
        draw(at: CGPoint.zero)
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

但是由于使用了boundingRect,这个函数在一行中返回了我的文本:

------------------------------------
|Some attributed text in trapezoid.|
------------------------------------

如果我要使用自定义矩形来绘制文本,那将无济于事...

UIGraphicsBeginImageContext(CGRect(x: 0, y: 0, width: 100, height: 30))
draw(at: CGPoint.zero)

...因为文本将在矩形中:

--------------
|Some attribu|
|ted text in |
|trapezoid.  |
--------------

我需要的是在具有已知角位置的梯形中(或在具有已知半径的圆中)绘制文本。所以每一行新的文本都应该以一个小的偏移量开始,见例子:

所以我想看到类似的东西:

---------------
\Some attribut/
 \ed text in /
  \trapezoid/
   ---------

我怎样才能达到这个结果?

【问题讨论】:

标签: swift uikit nsattributedstring uigraphicscontext


【解决方案1】:

您必须在此处下拉到 CoreText 级别。好消息是,您将能够以您想要的任何形状绘制文本!

extension NSAttributedString {
    public func draw(in path: CGPath) {
        let context = UIGraphicsGetCurrentContext()!

        let transform = CGAffineTransform(scaleX: +1, y: -1)

        let flippedPath = CGMutablePath()
        flippedPath.addPath(path, transform: transform)

        let range = CFRange(location: 0, length: 0)
        let framesetter = CTFramesetterCreateWithAttributedString(self)
        let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)

        context.saveGState()

        // Debug: fill path.
        context.setFillColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
        context.beginPath()
        context.addPath(path)
        context.fillPath()

        context.concatenate(transform)

        CTFrameDraw(frame, context)

        context.restoreGState()
    }
}

你可以像这样使用它:

let string = NSAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.")

let bounds = CGRect(x: 0, y: 0, width: 120, height: 120)

let path = CGMutablePath()
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 110, y: 10))
path.addLine(to: CGPoint(x: 90, y: 110))
path.addLine(to: CGPoint(x: 30, y: 110))
path.closeSubpath()

UIGraphicsBeginImageContextWithOptions(bounds.integral.size, true, 0)
defer { UIGraphicsEndImageContext() }

let context = UIGraphicsGetCurrentContext()!
context.setFillColor(UIColor.white.cgColor)
context.fill(.infinite)

string.draw(in: path)

let image = UIGraphicsGetImageFromCurrentImageContext()!

坏消息是,这个解决方案最后不会给你一个省略号。如果你真的想拥有它,你可能需要对 framesetter 给你的最后一行做一些调整。

【讨论】:

  • 酷,这就是我需要的。 The bad news is, this solution does not give you an ellipsis at the end - 如果路径是椭圆,它也适用于椭圆。你知道如何设置居中的水平文本对齐方式吗?
  • @VasiliiMuravev 您可以尝试使用属性字符串的段落样式。如果这不起作用,您可以通过向框架询问线条并单独绘制它们来自己水平居中线条。
【解决方案2】:

Christian Schnorr answer 一起为我的目的:

import PlaygroundSupport
import UIKit

extension NSAttributedString {

    /// Draws attributed string in selected CGPath.
    func draw(in path: CGPath) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        let transform = CGAffineTransform(scaleX: 1.0, y: -1.0)
        let flippedPath = CGMutablePath()
        flippedPath.addPath(path, transform: transform)
        let range = CFRange(location: 0, length: 0)
        let framesetter = CTFramesetterCreateWithAttributedString(self)
        let frame = CTFramesetterCreateFrame(framesetter, range, flippedPath, nil)
        context.saveGState()
        context.concatenate(transform)
        CTFrameDraw(frame, context)
        context.restoreGState()
    }

    /// Renders attributed string.
    ///
    /// - Parameters:
    ///   - size: A 'CGSize' for rendering string in trapezoid.
    ///   - degree: A `CGFloat`, representing trapezoid angles in degrees.
    /// - Returns: An optional `UIImage` with rendered string.
    func asTrapezoidImage(size: CGSize, degree: CGFloat) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        defer { UIGraphicsEndImageContext() }
        draw(in: size.trapezoidPath(degree))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

extension CGSize {

    /// Converts CGSize into trapezoid CGPath.
    ///
    /// - Parameter degree: A `CGFloat`, representing trapezoid angles in degrees.
    /// - Returns: A `CGPath` with trapezoid.
    func trapezoidPath(_ degree: CGFloat) -> CGPath {
        var offset = height * tan(CGFloat.pi * degree / 180.0)
        offset = max(0, min(width / 2.0, offset))
        let path = CGMutablePath()
        path.move(to: CGPoint.zero)
        path.addLine(to: CGPoint(x: width, y: 0.0))
        path.addLine(to: CGPoint(x: width - offset, y: height))
        path.addLine(to: CGPoint(x: offset, y: height))
        path.closeSubpath()
        return path
    }
}

使用:

let string = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
let attributes: [NSAttributedStringKey: Any] = [
    .foregroundColor: UIColor.blue,
    .backgroundColor: UIColor.white,
    .font: UIFont.systemFont(ofSize: 24.0)
]
let attrString = NSAttributedString(string: string, attributes: attributes)
let size = CGSize(width: 400.0, height: 120.0)
let image = attrString.asTrapezoidImage(size: size, degree: 12.0)
let imageView = UIImageView(image: image)
imageView.frame = CGRect(origin: CGPoint.zero, size: size)
PlaygroundPage.current.needsIndefiniteExecution = false
PlaygroundPage.current.liveView = imageView

注意允许的度数从 0° 到 90°

【讨论】:

    猜你喜欢
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多