Ios 如何在UILabel中使文本适合圆形

Ios 如何在UILabel中使文本适合圆形,ios,objective-c,ios7,uilabel,textkit,Ios,Objective C,Ios7,Uilabel,Textkit,我想将UILabel中的文本流成一个圆圈(而不是rect)。 我用NSLayoutManager、NSTextContainer和NSTextStorage做了一些实验,但似乎不起作用。 下面的示例应该将文本流到40x40(标签为120x120)的较小矩形中,但似乎没有任何效果 UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:12]; NSTextStorage *ts = [[NSTextStorage alloc] init

我想将
UILabel
中的文本流成一个圆圈(而不是rect)。 我用
NSLayoutManager
NSTextContainer
NSTextStorage
做了一些实验,但似乎不起作用。 下面的示例应该将文本流到40x40(标签为120x120)的较小矩形中,但似乎没有任何效果

UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:12];
NSTextStorage *ts = [[NSTextStorage alloc] initWithString:multiline.title attributes:@{NSFontAttributeName:font}];
NSLayoutManager *lm = [[NSLayoutManager alloc] init];
NSTextContainer *tc = [[NSTextContainer alloc] initWithSize:CGSizeMake(40, 40)];
[lm addTextContainer:tc];
[ts addLayoutManager:lm];
self.label.attributedText = ts;

Ides?

这似乎是一个非常简单的解决方案
NSTextContainer
具有一个
ExclutionPaths
属性。您可以做的是创建两个Bezier路径来定义应该排除的区域

所以我这样做了,下面是我的方法:

- (void)setCircularExclusionPathWithCenter:(CGPoint)center radius:(CGFloat)radius textView:(UITextView *)textView
{
    UIBezierPath *topHalf = [UIBezierPath bezierPath];
    [topHalf moveToPoint:CGPointMake(center.x - radius, center.y + radius)];
    [topHalf addLineToPoint:CGPointMake(center.x - radius, center.y)];
    [topHalf addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0.0f clockwise:NO];
    [topHalf addLineToPoint:CGPointMake(center.x + radius, center.y + radius)];
    [topHalf closePath];

    UIBezierPath *bottomHalf = [UIBezierPath bezierPath];
    [bottomHalf moveToPoint:CGPointMake(center.x - radius, center.y - radius)];
    [bottomHalf addLineToPoint:CGPointMake(center.x - radius, center.y)];
    [bottomHalf addArcWithCenter:center radius:radius startAngle:M_PI endAngle:0 clockwise:YES];
    [bottomHalf addLineToPoint:CGPointMake(center.x + radius, center.y - radius)];
    [bottomHalf closePath];

    textView.textContainer.exclusionPaths = @[bottomHalf, topHalf];
}
用法示例:

[self setCircularExclusionPathWithCenter:CGPointMake(160.0f, 200.0f)
                                  radius:100.0f
                                textView:_textView];
我的实验结果是:


当然,您必须使用UIExtView而不是UILabel,但我希望它能有所帮助:)

在UILabel中不能这样做,因为它不允许您访问TextKit堆栈。我要做的是构建自己的TextKit堆栈和子类NSTextContainer:

-(CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(CGRect *)remainingRect {
    CGRect result = [super lineFragmentRectForProposedRect:proposedRect atIndex:characterIndex writingDirection:baseWritingDirection remainingRect:remainingRect];
    CGRect r = CGRectMake(0,0,self.size.width,self.size.height);
    UIBezierPath* circle = [UIBezierPath bezierPathWithOvalInRect:r];
    CGPoint p = result.origin;
    while (![circle containsPoint:p]) {
        p.x += .1;
        result.origin = p;
    }
    CGFloat w = result.size.width;
    p = result.origin;
    p.x += w;
    while (![circle containsPoint:p]) {
        w -= .1;
        result.size.width = w;
        p = result.origin;
        p.x += w;
    }
    return result;
}
粗糙但有效。看起来像这样:


以下是我在Swift 3中对上述问题的贡献。

导入UIKit
导入核心文本
@可设计的
开放类ICoundLabel:UILabel{
//打开/关闭文本舍入,默认情况下处于打开状态
@IBInspectable open dynamic var IsRound:Bool=true{
迪塞特{
setNeedsDisplay()
}
}
//指定文本对齐方式
@可用(*,不可用,消息:“此属性保留给Interface Builder。请改用“roundedTextAlignment”。)
@i可检测的开放式动态var校准:UInt8{
设置{
self.roundedTextAlignment=CTTextAlignment(rawValue:newValue)!
setNeedsDisplay()
}
得到{
返回roundedTextAlignment.rawValue
}
}
//字号
@IBInspectable open dynamic var fillTextInCenter:Bool=true{
迪塞特{
setNeedsDisplay()
}
}
//字体步
@可用(*,不可用,消息:“此属性保留给Interface Builder。请改用“internalFontStep”。)
@IBInspectable open dynamic var fontStep:CGFloat{
设置(新值){
internalFontStep=最大值(新值,0.1)
}
得到{
返回内部方步
}
}
open var roundedTextAlignment:CTTextAlignment=.center
打开变量internalFontStep:CGFloat=1
覆盖打开的func drawText(在rect:CGRect中){
//检查是否需要自定义文本绘制
如果!被炸了{
super.drawText(in:rect)
返回
}
//检查文本是否存在
guard let text=self.text else{
返回
}
如果文本==“”{
返回
}
//获取图形上下文
guard let context=UIGraphicsGetCurrentContext()else{
返回
}
//标记:创建属性字符串
var stringRange=NSMakeRange(0,text.characters.count)
让attrString=CFAttributedStringCreate(kCFAllocatorDefault,文本为CFString!,attributedText?.attributes(at:0,effectiveRange:&stringRange)为CFDictionary!)
让attributedString=CFAttributedStringCreateMutableCopy(kCFAllocatorDefault,CFIndex.max,attrString)!
设stringLength=CFAttributedStringGetLength(attributedString)
//设置段落样式
设cfStringRange=CFRangeMake(0,stringLength)
let settings=[CTParagraphStyleSetting(规范:。对齐,值大小:MemoryLayout.size(of值:roundedTextAlignment),值:&roundedTextAlignment)]
让paragraphStyle=CTParagraphStyleCreate(设置,1)
CFAttributedStringSetAttribute(attributedString、cfStringRange、kCTParagraphStyleAttributeName、paragraphStyle)
//使用上下文进行自定义转换
context.translateBy(x:0.0,y:frame.size.height)
context.scaleBy(x:1.0,y:-1.0)
//带插入的新绘图矩形
设drawingRect=CGRect(原点:CGPoint(x:0,y:0),大小:CGSize(宽度:rect.size.width,高度:rect.size.height))
//将文本居中对齐
var boundingBox=text.boundingRect(带:drawingRect.size,选项:NSStringDrawingOptions.usesLineFragmentOrigin,属性:[NSFontAttributeName:font],上下文:nil)
//标记:创建椭圆路径
var path=CGPath(roundedRect:drawingRect,cornerWidth:drawingRect.width/2,cornerHeight:drawingRect.height/2,transform:nil)
//标记:帧和范围计算嵌套函数
func getTextFrameRange()->(CTFrame,CFRange){
设textFrame=CTFramesetterCreateFrame(CTFramesetterCreateWithAttributedString(attributedString),cfStringRange,path,nil)
让rangeThatFits=CTFrameGetVisibleStringRange(textFrame)
返回值(文本框,适合的范围)
}
var textFrame:CTFrame
var范围适合:CFRange
//标记:根据需要缩放字体大小
如果填充文本居中{
var fontSize=font.pointSize
var estimatedFont=字体大小(fontSize)
//将文本固定在初始矩形的中心
var-boxHeight=ceil(boundingBox.height)
func updateBoundingBox(){
boundingBox.origin=CGPoint(x:ceil((drawingRect.size.height-boxHeight)/2),y:ceil((drawingRect.size.height-boxHeight)/2))
boundingBox.size=CGSize(宽度:boxHeight,高度:boxHeight)
}
path=CGPath(roundedRect:boundingBox,cornerWidth:boundingBox.width/2,cornerHeight:boundingBox.height/2,transform:nil)
(u,rangeThatFits)=getTextFrameRange()
UpdateBondingBox()
//使文本居中
而cfStringRange.length!=rangeThatFits.length{
//如果需要,增加边界框大小
//或减小字体大小
如果boundingBox.widthimport UIKit
import CoreText

@IBDesignable
open class ICRoundLabel: UILabel {

// Switch on/off text rounding, is on by default
@IBInspectable open dynamic var isRounded:Bool = true {
    didSet{
        setNeedsDisplay()
    }
}

// Specify text alignment
@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'roundedTextAlignment' instead.")
@IBInspectable open dynamic var alignment:UInt8 {
    set{
        self.roundedTextAlignment = CTTextAlignment(rawValue: newValue)!
        setNeedsDisplay()
    }
    get{
        return roundedTextAlignment.rawValue
    }
}

// Font scale
@IBInspectable open dynamic var fillTextInCenter:Bool = true {
    didSet{
        setNeedsDisplay()
    }
}

// Font step
@available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'internalFontStep' instead.")
@IBInspectable open dynamic var fontStep:CGFloat {
    set(newValue) {
        internalFontStep = max(newValue, 0.1)
    }
    get {
        return internalFontStep
    }
}

open var roundedTextAlignment:CTTextAlignment = .center
open var internalFontStep:CGFloat = 1

override open func drawText(in rect: CGRect) {

    // Check if custom text draw is needed
    if !isRounded {
        super.drawText(in: rect)
        return
    }

    // Check if text exists
    guard let text = self.text else {
        return
    }

    if text == "" {
        return
    }

    // Get graphics context
    guard let context = UIGraphicsGetCurrentContext() else {
        return
    }

    //MARK: Create attributed string
    var stringRange = NSMakeRange(0, text.characters.count)
    let attrString = CFAttributedStringCreate(kCFAllocatorDefault, text as CFString!, attributedText?.attributes(at: 0, effectiveRange: &stringRange) as CFDictionary!)
    let attributedString = CFAttributedStringCreateMutableCopy(kCFAllocatorDefault, CFIndex.max, attrString)!
    let stringLength = CFAttributedStringGetLength(attributedString)

    // Set a paragraph style
    let cfStringRange = CFRangeMake(0, stringLength)
    let settings = [CTParagraphStyleSetting(spec: .alignment, valueSize: MemoryLayout.size(ofValue: roundedTextAlignment), value: &roundedTextAlignment)]
    let paragraphStyle = CTParagraphStyleCreate(settings, 1)

    CFAttributedStringSetAttribute(attributedString, cfStringRange, kCTParagraphStyleAttributeName, paragraphStyle)

    // Make custom transitions with context
    context.translateBy(x: 0.0, y: frame.size.height)
    context.scaleBy(x: 1.0, y: -1.0)

    // New drawing rect with insets
    let drawingRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: rect.size.width, height: rect.size.height))

    // Align text in center
    var boundingBox = text.boundingRect(with: drawingRect.size, options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)

    //MARK: Create elliptical path
    var path = CGPath(roundedRect: drawingRect, cornerWidth: drawingRect.width/2, cornerHeight: drawingRect.height/2, transform: nil)

    //MARK: Frame and range calculation nested function
    func getTextFrameRange() -> (CTFrame, CFRange) {
        let textFrame = CTFramesetterCreateFrame(CTFramesetterCreateWithAttributedString(attributedString), cfStringRange, path, nil)
        let rangeThatFits = CTFrameGetVisibleStringRange(textFrame)
        return (textFrame, rangeThatFits)
    }

    var textFrame:CTFrame
    var rangeThatFits:CFRange

    //MARK: Scaling font size if needed

    if fillTextInCenter {

        var fontSize = font.pointSize
        var estimatedFont = font.withSize(fontSize)

        // Pin text in center of initial rect
        var boxHeight = ceil(boundingBox.height)

        func updateBoundingBox() {
            boundingBox.origin = CGPoint(x: ceil((drawingRect.size.height - boxHeight)/2), y: ceil((drawingRect.size.height - boxHeight)/2))
            boundingBox.size = CGSize(width: boxHeight, height: boxHeight)
        }

        path = CGPath(roundedRect: boundingBox, cornerWidth: boundingBox.width/2, cornerHeight: boundingBox.height/2, transform: nil)

        (_, rangeThatFits) = getTextFrameRange()

        updateBoundingBox()

        // Fit text in center
        while cfStringRange.length != rangeThatFits.length {

            // Increase size of bounding box size if needed
            // or decrease font size
            if boundingBox.width < drawingRect.width {

                boxHeight += 1

                //Update bounding box accoringly to new box size
                updateBoundingBox()

                path = CGPath(roundedRect: boundingBox, cornerWidth: boundingBox.width/2, cornerHeight: boundingBox.height/2, transform: nil)

                (_, rangeThatFits) = getTextFrameRange()

                continue
            } else {

                CFAttributedStringSetAttribute(attributedString, cfStringRange, kCTFontAttributeName, estimatedFont)

                (_, rangeThatFits) = getTextFrameRange()

                // Increase or decrease font size
                fontSize += cfStringRange.length < rangeThatFits.length ? internalFontStep : -internalFontStep
                estimatedFont = font.withSize(fontSize)
            }
        }
    }

    //MARK: Draw the text frame in the view's graphics context
    (textFrame, _) = getTextFrameRange()
    CTFrameDraw(textFrame, context)

}

@IBInspectable var borderColor: UIColor = UIColor.white {
    didSet {
        layer.borderColor = borderColor.cgColor
    }
}

@IBInspectable var borderWidth: CGFloat = 1.0 {
    didSet {
        layer.borderWidth = borderWidth
    }
}

override open func layoutSubviews() {
    super.layoutSubviews()
    layer.cornerRadius = 0.5 * bounds.size.width
    clipsToBounds = true


}
var exclusionPaths: [UIBezierPath] { get set }
var usesEvenOddFillRule: Bool { get set }
var exclusionPath: UIBezierPath {
    let path = UIBezierPath(ovalIn: bounds)
    path.append(UIBezierPath(rect: bounds))
    path.usesEvenOddFillRule = true
    return path
}
import UIKit

class TextView: UITextView {

    convenience init() {
        self.init(frame: .zero, textContainer: nil)
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)

        isScrollEnabled = false
        isEditable = false
        textContainerInset = .zero
        self.textContainer.lineBreakMode = .byTruncatingTail
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    var exclusionPath: UIBezierPath {
        let path = UIBezierPath(ovalIn: bounds)
        path.append(UIBezierPath(rect: bounds))
        path.usesEvenOddFillRule = true
        return path
    }

}
extension TextView {

    // Draw circle

    override func draw(_ rect: CGRect) {
        UIColor.orange.setFill()
        let path = UIBezierPath(ovalIn: rect)
        path.fill()
    }

    // Draw exclusion path

    /*
     override func draw(_ rect: CGRect) {
         UIColor.orange.setFill()
         exclusionPath.fill()
     }
     */

}
import UIKit

class ViewController: UIViewController {

    let textView = TextView()

    override func viewDidLoad() {
        super.viewDidLoad()

        let string = "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."
        textView.attributedText = NSAttributedString(string: string)
        view.addSubview(textView)

        textView.translatesAutoresizingMaskIntoConstraints = false
        let horizontalConstraint = textView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        let verticalConstraint = textView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        let widthConstraint = textView.widthAnchor.constraint(equalToConstant: 240)
        let heightConstraint = textView.heightAnchor.constraint(equalToConstant: 240)
        NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])            
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        textView.textContainer.exclusionPaths = [textView.exclusionPath]
    }

}