[Swift][RESOLU][Affichage d'une NSAttributedString avec une contrainte de largeur]

DrakenDraken Membre
août 2015 modifié dans API UIKit #1

Je cherche à  calculer la taille nécessaire à  l'affichage d'une NSAttributedString, avec une contrainte de largeur. Il y a une méthode pour ça dans le SDK, que j'ai utilisé pour écrire une extension :



extension NSAttributedString {
func sizeContrainteH(contrainteH:CGFloat) -> CGSize {
let rect = self.boundingRectWithSize(CGSizeMake(contrainteH, CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
context:nil)
return CGSizeMake(ceil(rect.width), ceil(rect.height))
}
}

ça marche .. presque toujours ! De temps en temps, j'ai un résultat légèrement plus grand que la contrainte demandée.


 


Voici un code mettant le problème en évidence :



import UIKit

// Changement font
func changementFont (texte:NSAttributedString, font:UIFont) -> NSAttributedString
{
let texteMutable = NSMutableAttributedString(attributedString: texte)
texteMutable.addAttribute(NSFontAttributeName, value: font, range:NSRange(location:0, length:texte.length))
return NSAttributedString(attributedString: texteMutable)
}

// Cacul de la taille necessaire à  l'affichage, avec une contrainte horizontale
extension NSAttributedString {
func sizeContrainteH(contrainteH:CGFloat) -> CGSize {
let rect = self.boundingRectWithSize(CGSizeMake(contrainteH, CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
context:nil)
return CGSizeMake(ceil(rect.width), ceil(rect.height))
}
}


class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let font = UIFont(name: "ArialMT", size: 18.0)

let texte1 = "Lors d'une cérémonie d'accueil dans les jardins de la Maison Blanche sous un grand soleil, M. Obama a salué la place singulière du Japon \"l'un des alliés les plus proches des Etats-Unis\". \"Notre relation bilatérale est plus forte que jamais\", a de son côté souligné M. Abe."
let texte2 = "Etats-Unis et Japon saluent par ailleurs les \"progrès significatifs\" enregistrés dans leurs discussions bilatérales en vue d'aboutir à  un vaste accord de libre-échange en Asie-Pacifique qui représenterait 40% du PIB mondial et rassemblerait 12 pays, à  l'exception notable de la Chine."
let texte3 = "Le secrétaire d'Etat John Kerry a réaffirmé en début de semaine que cette alliance couvrait \"tous les territoires\" sous la responsabilité du Japon, \"y compris les à®les Senkaku\"."

let attrStr1 = changementFont(NSAttributedString(string: texte1), font!)
let attrStr2 = changementFont(NSAttributedString(string: texte2), font!)
let attrStr3 = changementFont(NSAttributedString(string: texte3), font!)

let textes = [attrStr1, attrStr2, attrStr3]

for phrase in textes {
let size = phrase.sizeContrainteH(320.0)
println(size)
}

}

}


Avec une police "ArialMT" de corps 18 et une contrainte de 320.0 points, j'obtiens les tailles suivantes :


 


(325,0  141,0)


(320,0  161,0)


(290,0  101,0)


 


Le premier texte ne respecte pas la contrainte de 320.0 points.


 


 


Il suffit de changer la taille de la police pour faire apparaà®tre ou disparaitre le problème. Exemple avec de l'ArialMT corps 17 : 


 


(307,0  133,0)


(320,0  152,0)


(324,0,  95,0)


 


C'est maintenant la troisième phrase qui ne respecte plus la contrainte. Grrr..


 


Le test fabrique les NSAttributedString avec du code, mais à  l'origine cela vient d'un traitement de texte. J'ai simplifié le problème pour le reproduire dans sa forme "pur".


 


Quelqu'un a une idée sur l'origine du problème ?


 


EDIT : J'ai utilisé le terme de contrainte parce qu'il me semblait approprié, mais cela n'a rien à  voir avec Storyboard et compagnie. C'est du code pur et dur, à  l'ancienne.


Réponses

  • AliGatorAliGator Membre, Modérateur
    août 2015 modifié #2
    Tu as pensé à  arrondir le résultat récupéré à  l'entier supérieur (ceil) ? Car c'est souvent un problème de subpixelling (tu utiliserais mon pod OHAttributedStringAdditions tu n'aurais pas le problème : ces lignes prennent ce genre de détail en compte :P)
  • Ton code :



    - (CGSize)sizeConstrainedToSize:(CGSize)maxSize
    {
    // Use NSStringDrawingUsesLineFragmentOrigin to compute bounds of multi-line strings (see Apple doc)
    CGRect bounds = [self boundingRectWithSize:maxSize
    options:NSStringDrawingUsesLineFragmentOrigin
    context:nil];

    // We need to ceil the returned values (see Apple doc)
    return CGSizeMake((CGFloat)ceil((double)bounds.size.width),
    (CGFloat)ceil((double)bounds.size.height) );
    }


    Mon code :



    extension NSAttributedString {
    func sizeContrainteH(contrainteH:CGFloat) -> CGSize {
    let rect = self.boundingRectWithSize(CGSizeMake(contrainteH, CGFloat.max),
    options: NSStringDrawingOptions.UsesLineFragmentOrigin,
    context:nil)
    return CGSizeMake(ceil(rect.width), ceil(rect.height))
    }
    }


    ça se ressemble beaucoup. Je vais quand même reprendre ton code et le retranscrire en swift, pour voir. 


     


    Les valeurs (ArialMT corps 18) sans arrondi :


     


    324.0615234375 140.765625


    319.623046875 160.875


    289.9072265625 100.546875

  • Reprise des opérations après une pause préparation sushis pour anniversaire !


     


    Voici ma copie de ta méthode :



    extension NSAttributedString {
    func sizeContrainteToSize(sizeMax:CGSize) -> CGSize {
    let rect = self.boundingRectWithSize(sizeMax,
    options: NSStringDrawingOptions.UsesLineFragmentOrigin,
    context: nil)
    return CGSizeMake(CGFloat(ceil(rect.size.width)),
    CGFloat(ceil(rect.size.height)))

    }
    }


    Les résultats sont identiques, y compris les dépassements de contrainte !


     


    Mon code original    : (325.0, 141.0)


    Ma copie de ton code : (325.0, 141.0)


    Mon code original    : (320.0, 161.0)


    Ma copie de ton code : (320.0, 161.0)


    Mon code original    : (290.0, 101.0)


    Ma copie de ton code : (290.0, 101.0)


     

  • AliGatorAliGator Membre, Modérateur
    août 2015 modifié #5
    Et t'as essayé en rajoutant l'option UsesDeviceMetrics ?

    Car malheureusement bien souvent les fichiers de police n'ont pas tous la même rigueur, et certains sont mal définis / configurés, du genre dans le fichier de police l'entrée qui décrit le caractère X indique qu'il fait 10 pixels alors qu'en vrai il en fait 12 une fois dessiné... ou la baseline de la police est indiquée dans le header comme étant à  tel position Y, mais en fait tu te rends compte que tous les glyphes et leur dessin dans le fichier de police sont dessinés un peu plus haut ou plus pas que cette position Y, ...)

    J'ai déjà  eu le cas souvent pour des fichiers de polices custom (un fichier TTF que tu rajoutes dans le bundle de ton application, et qui vient parfois d'on ne sait trop où, fourni par le client mais de qualité/finition/rigueur discutable, etc), pas souvenir de l'avoir eu pour des polices natives comme ArialMT mais après tout ça ne m'étonnerait pas tant que ça...
  • DrakenDraken Membre
    août 2015 modifié #6

    Nouvelle version avec ajout de l'option UsesDeviceMetrics



    extension NSAttributedString {
    func sizeContrainteToSize(sizeMax:CGSize) -> CGSize {
    let rect = self.boundingRectWithSize(sizeMax,
    options: NSStringDrawingOptions.UsesLineFragmentOrigin | NSStringDrawingOptions.UsesDeviceMetrics,
    context: nil)
    return CGSizeMake(CGFloat(ceil(rect.size.width)),
    CGFloat(ceil(rect.size.height)))

    }
    }


    La bonne nouvelle c'est qu'il n'y a plus de dépassement de contraintes.


     


    La mauvaise nouvelle c'est que les résultats sont toujours nuls :


     


    (0.0, 0.0)


    (0.0, 0.0)


    (0.0, 0.0)


  • DrakenDraken Membre
    août 2015 modifié #7

    Problème résolu en utilisant la technique préconisée par Apple à  cette adresse :


     


    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html



    func calculSize(texte:NSAttributedString, sizeMax:CGSize) -> CGSize {
    let textStorage = NSTextStorage(attributedString: texte)
    let textContainer = NSTextContainer(size: sizeMax)
    let layoutManager = NSLayoutManager()
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)
    textContainer.lineFragmentPadding = 0.0
    layoutManager.glyphRangeForTextContainer(textContainer)
    let size = layoutManager.usedRectForTextContainer(textContainer).size
    return CGSizeMake(ceil(size.width), ceil(size.height))
    }


    Test en ArialMT corps 18 (contrainte de 320.0 points)


     


    (320.0, 145.0)


    (320.0, 166.0)


    (290.0, 104.0)


     

     

    Test en ArialMT corps 17 :


     


    (307.0, 137.0)


    (320.0, 157.0)


    (320.0, 98.0)


     

     

    C'est curieux d'être obligé de sortir l'artillerie lourde plutôt qu'utiliser boudingRectWithSize.

  • AliGatorAliGator Membre, Modérateur
    Ah bah c'est sûr que si tu peux utiliser TextKit c'est mieux (puisque c'est ce qui est utilisé sous le capot, mais dispo que depuis iOS6 ou 7 c'est pour ça qu'il y'a encore pas mal de legacy code qui traine sur le net avec l'ancien moteur de rendu UIKit avant l'uniformisation avec CoreText dans TextKit)
Connectez-vous ou Inscrivez-vous pour répondre.