简体   繁体   English

如何在 iOS swift 中弯曲 label 文本

[英]how to Curve label text in iOS swift

I have a label which I want to make curve up and curve down using slider.我有一个 label,我想使用 slider 向上和向下弯曲。 I achieve this using following code:我使用以下代码实现了这一点:

func drawCurvedString(on layer: CALayer, text: NSAttributedString, angle: CGFloat, radius: CGFloat) {
var radAngle = angle.radians
let textSize = text.boundingRect(
with: CGSize(width: .max, height: .max),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
  .integral
  .size
let perimeter: CGFloat = 2 * .pi * radius
let textAngle: CGFloat = textSize.width / perimeter * 2 * .pi
var textRotation: CGFloat = 0
var textDirection: CGFloat = 0
if angle > CGFloat(10).radians, angle < CGFloat(170).radians {
// bottom string
    textRotation = 0.5 * .pi
    textDirection = -2 * .pi
    radAngle += textAngle / 2
  } else {
// top string
    textRotation = 1.5 * .pi
    textDirection = 2 * .pi
    radAngle -= textAngle / 2
  }
for c in 0..<text.length {
let letter = text.attributedSubstring(from: NSRange(c..<c+1))
let charSize = letter.boundingRect(
with: CGSize(width: .max, height: .max),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil)
    .integral
    .size
let letterAngle = (charSize.width / perimeter) * textDirection
let x = radius * cos(radAngle + (letterAngle / 2))
let y = radius * sin(radAngle + (letterAngle / 2))
let singleChar = drawText(
on: layer,
text: letter,
frame: CGRect(
x: (layer.frame.size.width / 2) - (charSize.width / 2) + x,
y: (layer.frame.size.height / 2) - (charSize.height / 2) + y,
width: charSize.width,
height: charSize.height))
    layer.addSublayer(singleChar)
    singleChar.transform = CATransform3DMakeAffineTransform(CGAffineTransform(rotationAngle: radAngle - textRotation))
    radAngle += letterAngle
  }
}
func drawText(on layer: CALayer, text: NSAttributedString, frame: CGRect) -> CATextLayer {
let textLayer = CATextLayer()
  textLayer.frame = frame
  textLayer.string = text
  textLayer.alignmentMode = kCAAlignmentCenter
  textLayer.contentsScale = UIScreen.main.scale
return textLayer
}

But the problem is its add a sublayer of text.但问题是它添加了一个文本子层。 All I want to make existing text curve using slider value.我只想使用 slider 值制作现有的文本曲线。 There is some code available but that is in objective-c so can anyone help how to achieve this without adding sublayer in swift.有一些代码可用,但在 objective-c 中,所以任何人都可以帮助如何实现这一点,而无需在 swift 中添加子层。 Thanks in advance.提前致谢。

Here is a complete example...这是一个完整的例子......

View Controller查看 Controller

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let testLabel = UILabelX()
        testLabel.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(testLabel)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain original image view Top / Leading / Trailing
            testLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
            testLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
            testLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
            // let's use the image's aspect ratio
            testLabel.heightAnchor.constraint(equalToConstant: 300.0),
            
        ])

        testLabel.backgroundColor = .yellow
        testLabel.text = "This is a test of the UILabelX subclass."
        
    }
    
}

UILabel subclass - from: https://stackoverflow.com/a/69056167/6257435 UILabel 子类- 来自: https://stackoverflow.com/a/69056167/6257435

@IBDesignable
class UILabelX: UILabel {
    
    @IBInspectable var angle: CGFloat = 1.6
    @IBInspectable var clockwise: Bool = true
    
    override func draw(_ rect: CGRect) {
        centreArcPerpendicular()
    }
    
    /**
     This draws the self.text around an arc of radius r,
     with the text centred at polar angle theta
     */
    func centreArcPerpendicular() {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        let str = self.text ?? ""
        let size = self.bounds.size
        context.translateBy(x: size.width / 2, y: size.height / 2)
        
        let radius = getRadiusForLabel()
        let l = str.count
        //        let attributes: [String : Any] = [NSAttributedString.Key: self.font]
        let attributes : [NSAttributedString.Key : Any] = [.font : self.font]
        
        let characters: [String] = str.map { String($0) } // An array of single character strings, each character in str
        var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
        var totalArc: CGFloat = 0 // ... and the total arc subtended by the string
        
        // Calculate the arc subtended by each letter and their total
        for i in 0 ..< l {
            //            arcs = [chordToArc(characters[i].widthOfString(usingFont: self.font), radius: radius)]
            arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: radius)]
            totalArc += arcs[i]
        }
        
        // Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
        // or anti-clockwise (right way up at 6 o'clock)?
        let direction: CGFloat = clockwise ? -1 : 1
        let slantCorrection = clockwise ? -CGFloat(Double.pi/2) : CGFloat(Double.pi/2)
        
        // The centre of the first character will then be at
        // thetaI = theta - totalArc / 2 + arcs[0] / 2
        // But we add the last term inside the loop
        var thetaI = angle - direction * totalArc / 2
        
        for i in 0 ..< l {
            thetaI += direction * arcs[i] / 2
            // Call centre with each character in turn.
            // Remember to add +/-90º to the slantAngle otherwise
            // the characters will "stack" round the arc rather than "text flow"
            centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
            // The centre of the next character will then be at
            // thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
            // but again we leave the last term to the start of the next loop...
            thetaI += direction * arcs[i] / 2
        }
    }
    
    func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
        // *******************************************************
        // Simple geometry
        // *******************************************************
        return 2 * asin(chord / (2 * radius))
    }
    
    /**
     This draws the String str centred at the position
     specified by the polar coordinates (r, theta)
     i.e. the x= r * cos(theta) y= r * sin(theta)
     and rotated by the angle slantAngle
     */
    func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
        // Set the text attributes
        let attributes = [NSAttributedString.Key.font: self.font!] as [NSAttributedString.Key : Any]
        // Save the context
        context.saveGState()
        // Move the origin to the centre of the text (negating the y-axis manually)
        context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
        // Rotate the coordinate system
        context.rotate(by: -slantAngle)
        // Calculate the width of the text
        let offset = str.size(withAttributes: attributes)
        // Move the origin by half the size of the text
        context.translateBy(x: -offset.width / 2, y: -offset.height / 2) // Move the origin to the centre of the text (negating the y-axis manually)
        // Draw the text
        str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
        // Restore the context
        context.restoreGState()
    }
    
    func getRadiusForLabel() -> CGFloat {
        // Imagine the bounds of this label will have a circle inside it.
        // The circle will be as big as the smallest width or height of this label.
        // But we need to fit the size of the font on the circle so make the circle a little
        // smaller so the text does not get drawn outside the bounds of the circle.
        let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
        let heightOfFont = self.text?.size(withAttributes: [NSAttributedString.Key.font: self.font]).height ?? 0
        
        // Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
        return (smallestWidthOrHeight/2) - heightOfFont + 5
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 Swift ios 如何找出标签文本是否被截断 - Swift ios how to find out Label text is truncated or not 如何在iOS swift4的for循环中更改标签文本? - How to change a label text during a for loop in IOS swift4? 1秒后iOS Swift更新文本标签 - iOS Swift Update text label after 1 second iOS / Swift:使用IBOutlet设置标签文本 - IOS/Swift: Set text of label with IBOutlet iOS Swift如何在标签点击时在TableView中获取标签文本 - IOS swift how can I get label text inside a TableView on label tap 将文本从iOS标签传递到WatchOS标签-swift- - Pass text from iOS label to WatchOS label - swift- 如何使用 swift 在 IOS 中实现跑马灯 label - how to implement marquee label in IOS using swift Swift iOS-如何设置UIView的高度锚&lt;=到标签的内在文本大小? &#39;NSLayoutConstraint&#39;不能转换为&#39;Bool&#39; - Swift iOS -How to set UIView's Height Anchor <= To A Label's Intrinsic Text Size? 'NSLayoutConstraint' is not convertible to 'Bool' 如何在 swift iOS 的 collectionview 中创建多行 label? - How to create multiline label in collectionview in swift iOS? 如何更改位于UIPageViewController的ViewController中的label.text? 迅速的iOS Xcode - How to change label.text located in UIPageViewController's ViewController? swift ios xcode
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM