Problème avec CATextLayer

ifouifou Membre
mai 2017 modifié dans API UIKit #1

Bonjour.


 


Ayant pas mal joué avec les UILabel, j'ai fini par me rendre compte que toutes les propriétés de cette classe ne sont pas candidates pour faire de l'animation. C'est le cas de la propriété "textColor".


 


J'ai fini par comprendre qu'il me faudrait utiliser une classe différente pour arriver à  cette fin: CATextLayer. En effet après avoir instancié cette classe, j'ai fini par arriver à  mes fins: Faire une animation de la couleur du texte du noir vers le rouge. Parfait. Ou presque.


 


En effet une fois cela fait, il m'était impossible d'appliquer les contraintes du LayoutEngine comme je le faisais avant sur mon UILabel. Il semblerait en effet que le LayoutEngine n'applique des contraintes que sur des instances de classes UIView (ou qui dérive de UIView).


 


Pas de problème. J'ai fini par comprendre que je pouvais implémenter ma propre classe qui dérive de UIView, tout en précisant d'utiliser le CATextLayer au lieu du CALayer. Pour cela il suffit de redéfinir la méthode LayerClass() et de retourner le type de CATextLayer.


 


Cependant depuis que je procède ainsi, j'ai un nouveau problème qui se manifeste que je n'ai jamais eu auparavant: Le texte affiché à  l'écran ne suis pas la taille de police que je souhaite.


 


Dans l'exemple "PlayGround" suivant, je vous présente ma classe "UITextLayerLabel" qui dérive de UIView. Cette classe est toute simple: en plus des deux constructeurs init() vous ne trouverez que l'override de LayerClass() qui sert à  spécifier à  la vue d'utiliser le CATextLayer comme layer, au lieu de l'habituel CALayer.


 


Plus bas, vous pourrez voir que j'instancie deux fois le textes "Toto". Une première fois sous la forme d'un UILabel banal que vous connaissez bien. Et juste ensuite je l'instancie sous la forme de ma nouvelle classe: "UITextLayerLabel". Dans les deux cas j'utilise la même font.


 


Or si vous exécutez ce bout de programme, vous pourrez constater comme moi que la police semble bien plus grosse dans le deuxième cas! Et je ne comprends pas ce comportement. J'ai beau cherche dans la doc d'Apple, je ne trouve pas. J'ai testé des myriades de choses, je serais bien même incapable de toutes les listés ici. Mais disons que mes recherches se centrées sur cette observation: il semblerait que dans le cas du deuxième label, le texte "Toto" est Up-scalé. Comme si il était grossi pour "Fitter" dans les bounds de la vue.


 


Je joint un screenshot ici, pour que nous soyons tous sur la même page si vous êtes amené à  exécuter ce bout de code dans XCode Playground.


 


ahle12n.png


 


 


Quelqu'un a-t-il une meilleure idée que moi sur le sujet?


 


Si vous pensez comme moi qu'il s'agit d'un problème de redimensionnement, quelle poste me suggérez-vous?


 


Merci d'avance pour vos suggestions. :-)


 


 


EDIT: Oups, et voici le code correspondant:



//: Playground - noun: a place where people can play

import UIKit
import PlaygroundSupport

let globalFont: UIFont = UIFont.systemFont(ofSize: 24, weight: UIFontWeightBlack)

class UITextLayerLabel : UIView {
override class var layerClass: AnyClass {
get {
return CATextLayer.self
}
}

override init(frame: CGRect) {
super.init(frame: frame)
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}


var initialFrame = CGRect(x: 0, y: 0, width: 100, height: 20)


let premierLabel = UILabel (frame: initialFrame)
premierLabel.text = "Toto"
premierLabel.textColor = UIColor.red
premierLabel.font = globalFont
premierLabel.layer.borderColor = UIColor.green.cgColor
premierLabel.layer.borderWidth = 1

initialFrame.origin.y += initialFrame.height
let secondLabel = UITextLayerLabel(frame: initialFrame)
secondLabel.backgroundColor = UIColor.clear

let textLayer = secondLabel.layer as! CATextLayer
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.font = globalFont
textLayer.string = "Toto"
textLayer.borderColor = UIColor.blue.cgColor
textLayer.borderWidth = 1

print("textlayer pos = \(textLayer.position), bounds = \(textLayer.bounds)")

let liveview = UIView()
liveview.backgroundColor = UIColor.white
liveview.frame = CGRect (x: 0, y: 0, width: 320, height: 600 )
liveview.addSubview(premierLabel)
liveview.addSubview(secondLabel)
PlaygroundPage.current.liveView = liveview

Réponses

  • DrakenDraken Membre
    mai 2017 modifié #2

    Là  comme ça, sans réfléchir je dirais que cela a un rapport avec le Rétina. Dans un cas, tu doit travailler avec des Points et dans l'autre avec des Pixels, d'où la différence de taille apparente. J'avais eu un problème similaire avec un contexte graphique bitmap. 


     


    Enfin c'est juste une hypothèse, n'ayant jamais travaillé avec des Layers. Tu devrais vérifier dans la doc si les infos (taille/position) des Layers sont en Pixels ou en Points.

  • LarmeLarme Membre

    Il manque ça :



    textLayer.fontSize = globalFont.pointSize
  • ifouifou Membre

    Oups. J'ai fini par trouver (sur le net). En réalité, il semble bien que le CATextLayer ignore la font size sur l'objet font. Il faut penser à  la spécifier à  côté dans la propriété "fontSize". (Je ne comprends pas pourquoi il y a une propriété à  côté. C'est vraiment casse-geule).


     


    Je viens de tester, et ça marche déjà  mieux les deux tailles ont l'air d'être bonne (à  peu près, pas totalement...):


     


    dUwinRH.png


     


     


    J'ai eu besoin de rajouter la dernière ligne ("textLayer.fontSize = 24"):



    let textLayer = secondLabel.layer as! CATextLayer
    textLayer.foregroundColor = UIColor.black.cgColor
    textLayer.font = globalFont
    textLayer.fontSize = 24
  • ifouifou Membre

    Ah oui bien vu Larme. Nos réponses se sont croisées. Mais toutes mes félicitations pour ta réponse! Tu as été plus rapide que moi... :-)


  • ifouifou Membre

    Maintenant j'ai besoin de voir pourquoi la hauteur du deuxième label n'est pas suffisante...


  • LarmeLarme Membre

    Si tu changes ça :



    var initialFrame = CGRect(x: 0, y: 0, width: 100, height: 50)

    Tu verras les complications arriver.


     


    Placer des caractères, c'est pas forcément évident, il y a des histoires de baselines... 


     


    Concernant la font, c'est pratique au contraire je trouve.


    Pour avoir jouer avec, c'est parfois chiant quand tu veux mettre une font custom par code, mais garder la même taille, il faut à  chaque fois appeler myLabel.font.pointSize pour créer la font. Alors que tout ce qu'il t'intéresse, c'est d'appeler la font via son nom et de garder sa taille.


  • DrakenDraken Membre

     


     


    Je viens de tester, et ça marche déjà  mieux les deux tailles ont l'air d'être bonne (à  peu près, pas totalement...):

     


    Je dirais que les deux tailles sont identiques, mais que la View utilise un anti-aliasing plus efficace que le Layer. Peut-être même d'ailleurs que le Layer n'utilise pas d'anti-aliasing du tout. Il y a peut-être une option a activer.

  • DrakenDraken Membre


     


    En réalité, il semble bien que le CATextLayer ignore la font size sur l'objet font. Il faut penser à  la spécifier à  côté dans la propriété "fontSize". (Je ne comprends pas pourquoi il y a une propriété à  côté. C'est vraiment casse-geule).


     




    D'après la doc c'est fait exprès pour pouvoir animer des changements de taille.

  • ifouifou Membre


    D'après la doc c'est fait exprès pour pouvoir animer des changements de taille.




    Ah ok. Cela ferait sens, en effet. J'ai beau chercher dans la page de CATextLayer je ne trouve pas, voire même en cliquant sur le champs "fontSize". Peut-être as-tu lu cela dans un des guides d'Apple? Il y en a tellement, et ils sont tous très long. C'est impossible de ne pas zapper des informations importantes...



  • Ah ok. Cela ferait sens, en effet. J'ai beau chercher dans la page de CATextLayer je ne trouve pas, voire même en cliquant sur le champs "fontSize". Peut-être as-tu lu cela dans un des guides d'Apple? Il y en a tellement, et ils sont tous très long. C'est impossible de ne pas zapper des informations importantes...



  • ifouifou Membre

    @Draken En effet, j'avais vu cela. Je pensais que tu faisais référence à  un paragraphe dans une doc qui aurait été plus explicite. Mais de toute manière, comme je l'écrivais plus haut dans le fil, ton explication fait sens.


     


    Quoiqu'il en soit, je vais me plonger dans le "Text Programming Guide For iOS" et ensuite le "Core Text Programming Guide", histoire de faire le tour de tout ce qui se fait.


     


    Je pense que le CATextLayer devrait le faire pour mon besoin dans mon application, cependant j'aime bien faire le tour de tout ce qui se fait. Merci pour vos réponses à  tout deux, Draken et Larme. :-)


  • DrakenDraken Membre
    mai 2017 modifié #13


    Maintenant j'ai besoin de voir pourquoi la hauteur du deuxième label n'est pas suffisante...




    Elle n'est pas suffisante parce que le CATextLayer est un esprit libre refusant les limitations dictatoriales que tu tentes de lui imposer !


     


    Regarde l'image accompagnant ce post. Elle contient 3 textes :


     


    - Le rectangle rouge est un UILabel de taille forcé, contenant le texte "Toto" en corps 24.



    label2.frame = CGRect(x: 0, y: 0, width: 100, height: 20)
    label2.center = CGPoint(x: 200, y: 50)


    Au moment de l'affichage, iOS s'arrange pour caser le texte dans les limites en rognant le haut et le bas.


     


    - Le rectangle bleu est un UILabel contenant exactement le même texte, mais dont la taille n'est pas prédéfini à  l'avance. Je lui donne juste la position du centre.



    label1.center = CGPoint(x: 50, y: 50)

    Après avoir défini le texte, j'appelle sizeToFit() qui ajuste automatiquement les dimensions du label en fonction de la "taille naturelle" de son contenu.



    label1.text = "Toto"
    // Calcul de la taille du Label en fonction de son contenu
    label1.sizeToFit()


    Tu peux voir sur l'image que le label (bleu) à  l'état "naturel" est plus haut que le label contraint (rouge).


     


    Le CATextLayer se fiche complètement des dimensions que tu lui donnes. Il affiche le texte avec sa marge haute "naturelle", sans se préoccuper de centrer l'image.


     


    Le rectangle vert est un CATextLayer, dont l'aspect est identique à  celui du UILabel bleu. Normal, puisque j'ai ajusté sa taille en fonction de celle du Label calculé par .sizeToFit().


     


    Tu peux aussi noter que mon CATextLayer n'a pas de problème d'anti-aliasing. J'ai ajusté les bons paramètres pour éviter ça.


     


    Le code complet de ma mini-démo :



    import UIKit

    class MonTextLayer : CATextLayer {

    private var _font = UIFont.systemFont(ofSize: 17)
    var fontLayer:UIFont {
    get { return _font }
    set {
    _font = newValue
    self.font = CGFont(newValue.fontName as NSString)
    self.fontSize = newValue.pointSize
    }
    }

    init(frame:CGRect) {
    super.init()
    self.frame = frame
    // Activation anti-aliasing pour un affichage propre
    self.allowsEdgeAntialiasing = true
    self.contentsScale = UIScreen.main.scale
    self.alignmentMode = "center"

    }

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

    class ViewController: UIViewController {

    override func viewDidLoad() {
    super.viewDidLoad()

    // Label Contraint et opprimé avec taille forcée
    let label2 = UILabel()
    self.view.addSubview(label2)
    label2.frame = CGRect(x: 0, y: 0, width: 100, height: 20)
    label2.center = CGPoint(x: 200, y: 50)
    label2.textAlignment = .center
    label2.font = UIFont.systemFont(ofSize: 24)
    label2.backgroundColor = UIColor.red
    label2.text = "Toto"

    // Label libre de gambader dans la prairie
    let label1 = UILabel()
    self.view.addSubview(label1)
    label1.center = CGPoint(x: 50, y: 50)
    label1.textAlignment = .center
    label1.font = UIFont.systemFont(ofSize: 24)
    label1.backgroundColor = UIColor.cyan
    label1.text = "Toto"
    // Calcul de la taille du Label en fonction de son contenu
    label1.sizeToFit()
    // Size necessaire à  l'affichage du texte
    let size = label1.frame.size
    print (size)

    // textLayer
    // (même taille que le UILabel "libre"
    // dont la taille est caculée avec .sizeToFit())
    let frame = CGRect(x: 100, y: 200, width: size.width, height: size.height)

    let textLayer = MonTextLayer(frame: frame)
    self.view.layer.addSublayer(textLayer)

    textLayer.fontLayer = UIFont.systemFont(ofSize: 24)
    textLayer.foregroundColor = UIColor.black.cgColor
    textLayer.backgroundColor = UIColor.green.cgColor
    // textLayer.borderColor = UIColor.blue.cgColor
    // textLayer.borderWidth = 1

    textLayer.string = "Toto"

    }


    }


Connectez-vous ou Inscrivez-vous pour répondre.