NSAttributedString: image qui s'adapte au containeur

Bonjour,


J'ai une NSAttributedString crée à  partir d'un code HTML et ça affiche des images mais elles ne sont pas adaptées au containeur c'est à  dire qu'elles dépassent l'écran. Comment faire pour les redimentionner ?


 


Merci d'avance,


Flow


Réponses

  • AliGatorAliGator Membre, Modérateur
    Bah tu parcourres tous les attributs de type NSAttatchmentAttributeName de ton NSAttributeString (autrement dit tous ses "attachements"/images attachées à  ta chaà®ne) et tu les remplacés par une version plus petite de la meme UIImage non ?
  • Comment faire pour remplacer par une version plus petite ?


  • AliGatorAliGator Membre, Modérateur

    Bah il faut générer l'image en plus petit, puis la remplacer :P


    Après pour générer une miniature d'une UIImage existante tu as déjà  des sujets qui traà®nent sur le sujet (perso j'ai un fait un pod il y a longtemps qui s'appelle UIImage-Resize dispo sur mon GitHub mais sur Google tu en trouves plein)


  • TheFlow_TheFlow_ Membre
    mars 2015 modifié #5

    J'ai trouvé quelque chose qui devrait faire l'affaire pour ça mais je suis un peu bloqué.



    content.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, content.length), options: nil, usingBlock: { (value:AnyObject!, range:NSRange, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
    if let attachment = value as? NSTextAttachment {
    // Executé 3 fois pour mes 3 images: Tout va bien !
    if let image = attachment.image {
    println("Image found !")
    // N'est pas exectuté
    }
    }
    })

    Je ne comprend pas pourquoi attachment.image n'est pas défini alors que ce sont des images et que image.fileType renvoie "public.png"


     


    EDIT: J'ai compris, cela doit être défini mais alors comment transformer mon NSTextAttachment en image ?


  • LarmeLarme Membre
    mars 2015 modifié #6

    Tu serais peut-être intéresser par cela: http://stackoverflow.com/questions/19671666/how-to-subclass-nstextattachment/21150241#21150241


     


    Si j'ai bien tout suivi : Subclassing de NSTextAttachment pour qu'il resize lui-même les images en fonction des settings (taille de la font, taille arbitraire, etc.)


  • DrakenDraken Membre
    mars 2015 modifié #7

    Je me suis un peu amusé avec la manipulations des images dans un NSAttributedString, pour un de mes projets. J'ai écris un code "SpaceInvader" parcourant un NSAttributedString et remplaçant toutes les images rencontrées par des aliens.


     


    Les graphismes sont de petites images png de 32x32 pixels.



    let imageChat = UIImage(named: "Chat")
    let imageSushis = UIImage(named: "Sushis")
    let imagePoisson = UIImage(named: "Poisson")
    let imageInvader = UIImage(named: "Invader")

    let Chat = NSTextAttachment()
    Chat.image = imageChat
    let stringChat = NSAttributedString(attachment: Chat)

    let Sushis = NSTextAttachment()
    Sushis.image = imageSushis
    let stringSushis = NSAttributedString(attachment: Sushis)

    let Poisson = NSTextAttachment()
    Poisson.image = imagePoisson
    let stringPoisson = NSAttributedString(attachment: Poisson)

    // CREATION DE LA PHRASE
    var maPhrase = NSMutableAttributedString(string: "Les ")
    maPhrase.appendAttributedString(stringChat)
    maPhrase.appendAttributedString(NSAttributedString(string:" aiment les "))
    maPhrase.appendAttributedString(stringSushis)
    maPhrase.appendAttributedString(NSAttributedString(string: " et les "))
    maPhrase.appendAttributedString(stringPoisson)

    // PREPARATION INVASION
    let uneInvasion = maPhrase

    uneInvasion.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, uneInvasion.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let textAttachement = value as? NSTextAttachment {
    // LECTURE IMAGE
    let image = textAttachement.image
    // TES TRAITEMENTS A TOI
    // ....
    // CREATION NOUVEAU NSTextAttachment
    let newAttribut = NSTextAttachment()
    newAttribut.image = imageInvader
    // DESTRUCTION ANCIEN NSTextAttachment
    uneInvasion.removeAttribute(NSAttachmentAttributeName, range: range)
    // ECRITURE NOUVEAU NSTextAttachment
    uneInvasion.addAttribute(NSAttachmentAttributeName, value: newAttribut, range: range)
    }
    }

    // AFFICHAGE DES PHRASES
    label1.attributedText = maPhrase
    label2.attributedText = uneInvasion



  • Le problème c'est que textAttachement.image renvoie nil...


  • DrakenDraken Membre
    mars 2015 modifié #9

    Chez moi, textAttachement.image contient bien une image UIImage. Est-ce que tu as essayé d'afficher la taille de l'image, comme dans le code ci-joint ? 


     


    Je précise que j'utilise Xcode bêta 6.3 (swift 1.2).



    uneInvasion.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, uneInvasion.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let textAttachement = value as? NSTextAttachment {
    // LECTURE IMAGE
    let image = textAttachement.image
    // TES TRAITEMENTS A TOI
    // ....

    // AFFICHAGE TAILLE IMAGE
    println(image!.size)

    // CREATION NOUVEAU NSTextAttachment
    let newAttribut = NSTextAttachment()
    newAttribut.image = imageInvader
    // DESTRUCTION ANCIEN NSTextAttachment
    uneInvasion.removeAttribute(NSAttachmentAttributeName, range: range)
    // ECRITURE NOUVEAU NSTextAttachment
    uneInvasion.addAttribute(NSAttachmentAttributeName, value: newAttribut, range: range)
    }
    }


  • LarmeLarme Membre
    mars 2015 modifié #10
    En bidouillant du côté de imageForBounds:textContainer:characterIndex:, j'ai pu apparemment récupérer l'image.
    [attributedString enumerateAttribute:NSAttachmentAttributeName
              inRange:NSMakeRange(0, [attributedString length])
              options:0
              usingBlock:^(id value, NSRange range, BOOL *stop)
      {
       if ([value isKindOfClass:[NSTextAttachment class]])
       {
        NSTextAttachment *attachment = (NSTextAttachment *)value;
        if (range.location == 0)
        {
         UIImage *img2 = [attachment imageForBounds:[attachment bounds] textContainer:nil characterIndex:range.location];
         [imgView setImage:img2];
        }
       }
      }];
    @Draken: Je pense que ça dépend d'où vient l'image. Dans le cas d'HTML, j'pense qu'il faudrait faire un truc comme j'ai fait. Donc à  vérifier si attachment.image est nil et si le fileType correspond bien à  une image.
  • TheFlow_TheFlow_ Membre
    mars 2015 modifié #11

    Draken: Non, c'est bien une optionnel vide


     


    Larme: textContainer ne peut pas être nil de plus, la condition range.location == 0 ne semble jamais être remplie dans mon cas


  • Pardon, le range.location == 0 est dans mon cas de test (j'ai mis 2 images, et j'voulais vérifier que j'arrivais bien à  récupérer la première image et la mettre dans une UIImageView.

    J'ai pu mettre le paramètre textContainer à  nil sans problème dans mon cas. Faudrait peut-être chercher sinon ce qu'on pourrait dedans.


  • TheFlow_TheFlow_ Membre
    mars 2015 modifié #13

    Effectivement ça a l'air de fonctionner !


     


    Je cherche maintenant à  redimensionner mon image.


    Voilà  mon code, l'image est bien redimensionnée (avec une fonction maison) quand j'affiche les nouvelles tailles dans la console mais la nouvelle image n'a pas l'air de s'afficher:



    content.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, content.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let attachement = value as? NSTextAttachment {
    let image = attachement.imageForBounds(attachement.bounds, textContainer: NSTextContainer(), characterIndex: range.location)
    let screenSize: CGRect = UIScreen.mainScreen().bounds
    println("FOUND 1 IMAGE")
    image.resize(CGSize(width: screenSize.width, height: screenSize.width*image.size.height/image.size.width), completionHandler: { resizedImage in
    println("\(resizedImage.size)")
    dispatch_async(dispatch_get_main_queue(), {
                            content.removeAttribute(NSAttachmentAttributeName, range: range)
                            content.addAttribute(NSAttachmentAttributeName, value: resizedImage, range: range)
                        })
    })
    }
    }

  • Une fonction pour changer la taille d'une image :



    func resizeImage(image: UIImage, scale: CGFloat) -> UIImage {

    let newSize = CGSizeMake(image.size.width*scale, image.size.height*scale)
    let rect = CGRectMake(0, 0, newSize.width, newSize.height)

    UIGraphicsBeginImageContext(newSize)
    image.drawInRect(rect)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage
    }


    J'ai modifié ma séquence "invader" pour réduire les images à  75% au lieu de les remplacer par un alien.



    uneInvasion.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, uneInvasion.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let textAttachement = value as? NSTextAttachment {
    // LECTURE IMAGE
    let image = textAttachement.image

    // CREATION IMAGE REDUITE
    let newImage = resizeImage(image!, 0.75)

    // CREATION NOUVEAU NSTextAttachment
    let newAttribut = NSTextAttachment()
    newAttribut.image = newImage
    // DESTRUCTION ANCIEN NSTextAttachment
    uneInvasion.removeAttribute(NSAttachmentAttributeName, range: range)
    // ECRITURE NOUVEAU NSTextAttachment
    uneInvasion.addAttribute(NSAttachmentAttributeName, value: newAttribut, range: range)
    }
    }


    Résultat en image :


     


     


  • ça marche super ! Merci beaucoup à  tous !


     


    Voilà  mon code final:



    content.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, content.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let attachement = value as? NSTextAttachment {
    let image = attachement.imageForBounds(attachement.bounds, textContainer: NSTextContainer(), characterIndex: range.location)
    let screenSize: CGRect = UIScreen.mainScreen().bounds
    if image.size.width > screenSize.width {
    let newImage = image.resizeImage(screenSize.width/image.size.width)
    let newAttribut = NSTextAttachment()
    newAttribut.image = newImage
    content.addAttribute(NSAttachmentAttributeName, value: newAttribut, range: range)
    }
    }
    }

    Draken, j'ai modifié ta fonction pour l'intégrer directement à  UIImage via une extension:



    extension UIImage {
    func resizeImage(scale: CGFloat) -> UIImage {

    let newSize = CGSizeMake(self.size.width*scale, self.size.height*scale)
    let rect = CGRectMake(0, 0, newSize.width, newSize.height)

    UIGraphicsBeginImageContext(newSize)
    self.drawInRect(rect)
    let newImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return newImage
    }
    }
  • DrakenDraken Membre
    mars 2015 modifié #16


     


    Effectivement ça a l'air de fonctionner !


     


    Je cherche maintenant à  redimensionner mon image.


    Voilà  mon code, l'image est bien redimensionnée (avec une fonction maison) quand j'affiche les nouvelles tailles dans la console mais la nouvelle image n'a pas l'air de s'afficher:



    content.enumerateAttribute(NSAttachmentAttributeName, inRange: NSMakeRange(0, content.length), options: NSAttributedStringEnumerationOptions(0)) { (value, range, stop) -> Void in
    if let attachement = value as? NSTextAttachment {
    let image = attachement.imageForBounds(attachement.bounds, textContainer: NSTextContainer(), characterIndex: range.location)
    let screenSize: CGRect = UIScreen.mainScreen().bounds
    println("FOUND 1 IMAGE")
    image.resize(CGSize(width: screenSize.width, height: screenSize.width*image.size.height/image.size.width), completionHandler: { resizedImage in
    println("\(resizedImage.size)")
    dispatch_async(dispatch_get_main_queue(), {
                            content.removeAttribute(NSAttachmentAttributeName, range: range)
                            content.addAttribute(NSAttachmentAttributeName, value: resizedImage, range: range)
                        })
    })
    }
    }



    Ton code ne risque pas de fonctionner. Il faut détruire l'ancien NSTextAttachment, en créer un nouveau et le stocker dans la chaine. Toi, tu détruis l'ancien NSTextAttchement sans en créer un autre. Tu transmets une UIImage alors qu'il faudrait un NSTextAttachment encapsulant l'UIImage. Le compilateur ne repère pas l'erreur, parce que le paramètre Value étant de type AnyObject, peut contenir n'importe quoi.


  • Euh, si j'ai bien tout suivi, ton idée, c'est de faire :
    stringHTML => NSAttributedString => RedimensionnementImage du NSAttributedString => Affichage


    Du coup, le dispatch main queue, il sert à  quoi exactement ? Et pour rappel, normalement, il n'y a pas besoin de supprimer l'ancien NSTextAttachment, car le tout est mis dans un dictionnaire avec la clé NSAttachmentAttributeName, or un dictionnaire (et si on garde le même range dans notre cas pour l'application de ce dernier), y'a unicité de la valeur.


  • AliGatorAliGator Membre, Modérateur
    content.addAttribute(NSAttachmentAttributeName, value: resizedImage, range: range)
    Comme souligné par Draken, l'attribut NSAttachmentAttributeName d'une NSAttributesString est sensé être de type NSTextAttachment (comme indiqué dans la doc), alors que toi tu lui affectes directement une UIImage. Il faut que tu mettes un NSTextAttachment quand tu réaffectes cet attribut.
  • Mon problème est résolu (voir mon dernier post), merci à  tous effectivement j'avais aussi oublié de passer un NSTextAttachment.


  • Ah, je suis face à  un nouveau problème:


    Je viens d'installer Xcode 6.2, avec le simulateur d'iOS 8.2 donc et la fonction resizeImage() provoque ce message d'erreur dans la console:


    void SendDelegateMessage(NSInvocation *): delegate (<CFNotificationCenter 0x7f9c0153ac60 [0x108dd2eb0]>) failed to return after waiting 10 seconds. main run loop mode: kCFRunLoopDefaultMode

     



    Je ne comprend pas pourquoi, tout fonctionnait bien avec Xcode 6.1 et iOS 8.1...


  • Pourtant ça fonctionne avec Xcode 6.3 Beta !


  • Bon apparemment c'est juste l'extension qui buguait...


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