2017-07-24 4 views
4

Imaginons que j'ai un texte: «Un texte attribué à trapézoïde »Comment dessiner du texte en trapèze (ou en cercle)?

J'ai l'extension NSAttributedString, qui me retourne UIImage avec le texte attribué:

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() 
    } 
} 

Mais cette fonction me renvoie du texte en une ligne, en raison de l'utilisation boundingRect:

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

Si j'utiliser rect sur mesure pour l'affichage de texte, il ne va pas aider beaucoup ...

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

... en raison du texte sera dans le rectangle:

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

ce que je dois, est de dessiner le texte dans un trapézoïdal avec des positions d'angle connues (ou dans un cercle de rayon connu). Ainsi, chaque nouvelle ligne de texte devrait commencer par un petit décalage, voir par exemple: enter image description here

Je veux voir quelque chose comme ça:

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

Comment puis-je obtenir ce résultat?

+1

Cet article fournit un bon début . https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html#//apple_ref/doc/uid/TP40009542-CH4-SW1 – BallpointBen

+1

Pour les formes circulaires, je pense que voici votre solution: [Comment adapter le texte dans un cercle dans UILabel] (https://stackoverflow.com/a/22179104/2124535) Et l'utiliser pour un trapèze * ne devrait pas * (ne pas testé) être si loin car cela signifierait définir les zones exclues comme 2 triangles rectangles – nathan

+1

Ajout à BallPointBen, peut-être quelques idées: https://stackoverflow.com/questions/26632474/uitextview-textcontainer-exclusion-path-fails-if-full-width-and -positioned-at-to – Larme

Répondre

1

Vous devrez descendre aux niveaux CoreText ici. La bonne nouvelle est, vous serez en mesure de dessiner du texte dans à peu près n'importe quelle forme que vous souhaitez!

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() 
    } 
} 

Et vous pouvez l'utiliser comme ceci:

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()! 

Les mauvaises nouvelles sont, cette solution ne vous donne pas de points de suspension à la fin. Si vous voulez vraiment avoir cela, vous devrez peut-être faire quelques ajustements à la dernière ligne que le framesetter vous donne.

+0

Cool, c'est ce dont j'ai besoin. 'Les mauvaises nouvelles sont, cette solution ne vous donne pas une ellipse à la fin' - cela fonctionne aussi avec l'ellipse, si le chemin est une ellipse. Savez-vous comment définir l'alignement du texte horizontal pour le centre? –

+0

@VasiliiMuravev Vous pouvez essayer de jouer avec le style de paragraphe de la chaîne attribuée. Si cela ne fonctionne pas, vous pouvez centrer les lignes horizontalement vous-même en demandant au cadre les lignes et en les dessinant individuellement. –

0

Avec Christian Schnorr answer fait pour mes besoins:

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 
    } 
} 

utilisant:

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 

Remarque degrés autorisés de 0 ° à 90 °