[SWIFT 3] CollectionView et contrainte

InsouInsou Membre

Hello tout le monde,


 


J'ai un petit soucis de contrainte avec une collectionView..


En gros, je suis en train de développer une discussion de groupe, chacun peut envoyer des messages dans un groupe... un peu comme Whatsapp ou Messenger (en vachement plus lite ^^)


 


ça se passe relativement bien mais là  je bloque sur un truc.. je pense qu'il me manque une contrainte mais impossible de réussir ce que je veux faire..


 


Voila où j'en suis : 


 


«1

Réponses

  • CéroceCéroce Membre, Modérateur

    Commence par nous dire comment sont fixées les contraintes. 


    A priori, tu as une contrainte bottom = 0 entre la collection view et sa vue contenante, alors que ça devrait être avec le container.


  • InsouInsou Membre

    J'vais essayé de poster tout le code utile.. C'est vrai qu'on y verra surement plus clair ^^



    class DiscussionCollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {

    let messageInputContainerView: UIView = {
    let view = UIView()
    view.backgroundColor = UIColor.clear
    return view
    }()

    let inputTextfield: UITextField = {
    let textfield = UITextField()
    textfield.placeholder = "Votre message.."
    return textfield
    }()

    var contrainteViewPourTextfield: NSLayoutConstraint!

    let sendButton: UIButton = {
    let button = UIButton(type: .custom)
    button.setImage(UIImage(named: "envoyer"), for: .normal)
    button.addTarget(self, action: #selector(actionEnvoyerMessage), for: .touchUpInside)
    return button
    }()

    override func viewDidLoad() {
    super.viewDidLoad()
    NbUtilisateur = TabInfos["NbUtilisateur"] as! Int

    chargeDiscussion()

    self.title = TabInfos["NomGroupe"] as! String?
    self.collectionView!.register(ChatLogMessageCell.self, forCellWithReuseIdentifier: reuseIdentifier)

    view.addSubview(messageInputContainerView)
    view.ajouteContrainte(format: "H:|[v0]|", views: messageInputContainerView)
    view.ajouteContrainte(format: "V:[v0(48)]", views: messageInputContainerView)

    // Contrainte bottom pour le messageInputContainerView - DEBUT
    contrainteViewPourTextfield = NSLayoutConstraint(item: messageInputContainerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
    view.addConstraint(contrainteViewPourTextfield)
    // Contrainte bottom pour le messageInputContainerView - FIN

    setupInputComponents()

    // ajoute un observer sur le fait de faire apparaitre le clavier (déclenche le selecteur)
    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    // ajoute un observer sur le fait de faire disparaitre le clavier (déclenche le selecteur)
    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)

    SwiftOverlays.removeAllBlockingOverlays()
    }


    // Lorsqu'on clic sur le textfield et que ça déploi le clavier
    // on change la contrainte bottom du messageInputContainerView
    // Pour afficher le textfield au dessus du clavier
    func handleKeyboardNotification(notification: NSNotification){
    if let userInfo = notification.userInfo {
    let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
    let taille = -(keyboardFrame?.height)!
    //print(taille)

    let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
    self.contrainteViewPourTextfield?.constant = isKeyboardShowing ? taille : 0 // Si on fait apparaitre le clavier, on change la taille de la contrainte, sinon on la remet à  0

    UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
    self.view.layoutIfNeeded()
    }, completion: {
    (completed) in
    if(isKeyboardShowing){
    // Scroll jusqu'au dernier message
    // lorsqu'on affiche le clavier
    self.scrollToBottom()
    }
    })
    }
    }

    private func setupInputComponents(){
    let topBorderView = UIView()
    topBorderView.backgroundColor = UIColor.orange

    messageInputContainerView.addSubview(inputTextfield)
    messageInputContainerView.addSubview(sendButton)
    messageInputContainerView.addSubview(topBorderView)

    messageInputContainerView.ajouteContrainte(format: "H:|-8-[v0][v1(60)]|", views: inputTextfield, sendButton)
    messageInputContainerView.ajouteContrainte(format: "V:|[v0]|", views: inputTextfield)
    messageInputContainerView.ajouteContrainte(format: "V:|[v0]|", views: sendButton)

    messageInputContainerView.ajouteContrainte(format: "H:|[v0]|", views: topBorderView)
    messageInputContainerView.ajouteContrainte(format: "V:|[v0(0.5)]", views: topBorderView)

    }
    }


    extension UIView {
    func ajouteContrainte(format: String, views: UIView...){

    var viewsDictionnary = [String:UIView]()
    for(index, view) in views.enumerated(){
    let key = "v\(index)"
    viewsDictionnary[key] = view
    view.translatesAutoresizingMaskIntoConstraints = false
    }

    addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionnary))
    }
    }

    Voilà , il y a tout ce qui concerne les contraintes.. le reste c'est des fonctions pour créer les bulles, charger la discussion, envoyer un message, etc etc.. 


    ça n'a pas d'influence sur les contraintes :)


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #4
    Pourquoi tu utilises le code pour construire tes vues et contraintes ?


    En utilisant IB, tu verras immédiatement où se trouve les soucis l'agencement.


    Et, surtout, arrêtes-toi d'utiliser les ! !!!
  • InsouInsou Membre

    Parce que j'ai suivis un tuto qui faisait comme ça.. sur le coup c'était plus simple pour commencer.


    Donc pas d'IB pour cette fois :/


  • Joanna CarterJoanna Carter Membre, Modérateur

    Il y a les tutos et les tutos. Si tu a bien suivi le tuto, pourquoi tu as les soucis ?


     


    Et, si un tuto t'apprend d'utiliser les !s comme tu l'as fait, c'est un très mauvais tuto !


     


    Côté simplicité, créer les vues et contraintes en IB, c'est beaucoup plus facile que le code car IB te montrera, en couleur distincte, ou se trouve les fautes.


     


    Mais, après tout ça, je te demande : est-ce que tu as commencer avec une UICollectionViewController avec la vue déjà  là  dedans?


     


    Si oui, voilà  ! Tes problèmes commencent là .


     


    Pour tel agencement que tu veuilles, il faut commencer ave une UIView qui contient une UICollectionView et une UITextView. Puis, tu devras changer la classe du contrôleur de UIViewController à  UICollectionViewController et reconnecter tous les outlet et delegates par main en IB.


     


    Crois moi, avec IB tu gaspilleras beaucoup moins de temps.


  • InsouInsou Membre

     


    Il y a les tutos et les tutos. Si tu a bien suivi le tuto, pourquoi tu as les soucis ?



     


    Bonne question.. puis j'ai pris que ce qui m'intéressait dans le tuto.. il y a forcément des trucs que j'ai dû adapter avec mon code (le chargement des messages, l'envoi de message, etc etc)


     



     


    Et, si un tuto t'apprend d'utiliser les !s comme tu l'as fait, c'est un très mauvais tuto !



     


    J'attendais ta réflexion sur ça :P


     



    Mais, après tout ça, je te demande : est-ce que tu as commencer avec une UICollectionViewController avec la vue déjà  là  dedans?



     


    Je suis pas sûr de comprendre mais je crois que oui..


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    OK, le tout en moins de 45 minutes



    class ChatViewController : UIViewController, UICollectionViewDelegate, UICollectionViewDataSource
    {
    @IBOutlet weak var bottomConstraint: NSLayoutConstraint!

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

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
    }

    deinit
    {
    NotificationCenter.default.removeObserver(self)
    }

    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
    {
    return 10
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)

    return cell
    }

    func keyboardWillShow(notification: Notification)
    {
    if let userInfo = notification.userInfo,
    let keyboardRectValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue
    {
    let keyboardRect = keyboardRectValue.cgRectValue

    self.bottomConstraint.constant = -keyboardRect.height
    }
    }

    func keyboardWillHide(notification: Notification)
    {
    self.bottomConstraint.constant = 0
    }
    }

  • InsouInsou Membre

    ça ressemble pas mal a ce que j'ai commencé à  faire, d'un côté c'est rassurant ^^


     


    Je continuerai ça mardi (j'vais profiter de quelques jours de congés ^^) mais en tout cas, merci Joanna pour l'aide et les explications :D


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #10
    Les méthodes pour le clavier sont connectées dans l'init et il faut les déconnecter dans la deinit pour l'équilibre.


    Dans les méthodes pour le clavier il ne faut que regler le constant sur le contrainte.


    Et il n y a qu'une ! pour le IBOutlet ;)


    Mais notes bien quil n'y a que très peu de code ; c'est beaucoup plus facile d'utiliser IB, n'importe quoi que les tutos disent.
  • InsouInsou Membre

    En effet, c'est plus simple comme ça.. 


    Ce qui m'inquiète c'est comment je vais refaire les bulles de conversation.. A moins que je garde cette partie en code en espérant que ça s'intègre bien.. Mais bon, j'en suis pas encore là , je vais faire petit à  petit ^^ 


  • Joanna CarterJoanna Carter Membre, Modérateur

    Tu écris un "layout" dans une classe à  part




  •  


    Je continuerai ça mardi (j'vais profiter de quelques jours de congés ^^)




    Tu .. tu vas avoir quelques jours de congés à  partir de mardi ?? Mon dieu .. Hollande est dans notre bar et nous ne le savions pas !  ???

  • InsouInsou Membre

    Bon, je suis de retour et je continue donc sur mon soucis de chat..


     


    J'ai refais mon storyboard avec la contrainte "bottom", comme tu me l'as expliqué.


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Tu as changé le type de la cellule dans le UICollectionView ?


  • InsouInsou Membre

    Je ne vois pas vraiment ce que tu veux dire :s


  • Joanna CarterJoanna Carter Membre, Modérateur
  • InsouInsou Membre

    Ah oui ok.. c'est c'que j'me disais mais comme j'avais rien dans l'auto-complétion, j'pensais que je faisais fausse route.


     


    J'ai donc créer un nouveau fichier UICollectionViewCell, appelé : ChatLogMessageCollectionViewCell avec comme code :



    import UIKit

    class ChatLogMessageCollectionViewCell: UICollectionViewCell {

    let expediteurTextView: UITextView = {
    let textView = UITextView()
    textView.font = UIFont.boldSystemFont(ofSize: 16)
    textView.text = "Expéditeur"
    textView.backgroundColor = UIColor.clear
    return textView
    }()

    let messageTextView: UITextView = {
    let textView = UITextView()
    textView.font = UIFont.systemFont(ofSize: 16)
    textView.text = "Message d'exemple !"
    textView.backgroundColor = UIColor.clear
    return textView
    }()

    let bubbleTextView: UIView = {
    let view = UIView()
    //view.backgroundColor = UIColor(white: 0.95, alpha: 1)
    //view.layer.cornerRadius = 15
    view.layer.masksToBounds = true
    return view
    }()

    static let grayBubbleImage = UIImage(named: "bubblegray")!.resizableImage(withCapInsets: UIEdgeInsets(top: 22,left: 28,bottom: 22,right: 26)).withRenderingMode(.alwaysTemplate)
    static let blueBubbleImage = UIImage(named: "bubbleblue")!.resizableImage(withCapInsets: UIEdgeInsets(top: 22,left: 28,bottom: 22,right: 26)).withRenderingMode(.alwaysTemplate)

    let bubbleImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.image = ChatLogMessageCell.grayBubbleImage
    imageView.tintColor = UIColor(white: 0.80, alpha: 1)
    return imageView
    }()

    func setupViews() {

    addSubview(bubbleTextView)
    addSubview(expediteurTextView)
    addSubview(messageTextView)

    bubbleTextView.addSubview(bubbleImageView)
    bubbleTextView.ajouteContrainte(format: "H:|[v0]|", views: bubbleImageView)
    bubbleTextView.ajouteContrainte(format: "V:|[v0]|", views: bubbleImageView)
    }
    }


    J'ai changé le type de la cellule par ChatLogMessageCollectionViewCell et j'ai changé ma ligne :



    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ChatLogMessageCell

    par 



    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! ChatLogMessageCollectionViewCell

    Par contre, j'ai rien qui s'affiche :/


    Je passe bien dans mes fonctions "CreerMonMessage"


     


    J'ai dû louper un truc.. Une idée ?


  • Joanna CarterJoanna Carter Membre, Modérateur
    mai 2017 modifié #19

    Tu as changer la classe de la cellule dans IB, comme je t'ai montré dans le screenshot ?


     


    Il ne fallait pas changer le nom de la classe


  • InsouInsou Membre
    mai 2017 modifié #20

    Oui :)


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Tu as déjà  créé une classe ChatLogMessageCell, qui dérive de BaseCell, dont j'ai supposé de dériver de UICollectionViewCell.


     


    Si ce n'est pas le cas, tu as raison de créer une autre classe qui dérive de UICollectionViewCell.


  • InsouInsou Membre

    Bon alors en fait, quand je disais que je n'avais pas de cellule.. c'est juste que je ne les avais pas vu..


    J'ai mis leur background en rouge, dans Content mode, j'ai mis : Aspect Fit et voila ce que j'ai :


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Tu as créé la cellule visuellement, soit dans le storyboard, soit dans un XIB, avec tout ce qu'il faut comme UILabel, etc ?


  • InsouInsou Membre

    Pour l'instant, ma storyboard ressemble à  ça :


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Jamais. Tu peux les mettre en place, avec les contraintes, dans la cellule dans le storyboard. C'est beaucoup plus facile que les construire en code.


     


    Et, peut-être, tu pourrais faire deux cellules ; l'une pour les message reçus, l'autre pour les message émis.


  • InsouInsou Membre

    Du coup, ça me sert à  quoi de mettre ChatLogMessageCollectionViewCell dans la cellule si ça ne se sert pas du code pour créer le model de ma cellule ?  :o


     


    Peux tu me faire un exemple pour une cellule ? (enfin quand tu aura un peu de temps pour ça bien sûr)


    Parce que là , je suis bloque et je suis complètement perdu :/


  • Joanna CarterJoanna Carter Membre, Modérateur

    J'ai changé les UITextViews pour les UILabels mais l'idée est pareille


     


  • InsouInsou Membre

    Ok je vois, c'est dans la classe de la cellule que je dois mettre les IBOutlets... je les mettais dans la classe de base.. du coup, j'avais toujours une erreur.. mais c'est plus logique maintenant que je comprends ^^


     


    Du coup, par manque de temps dans le projet, j'ai mixé les 2 techniques (la page en storyboard, les cellules en programmation..)


    Vu que j'avais déjà  le code pour faire les bulles, etc etc, c'était plus rapide (pour cette fois ci).


    Par contre je confirme, c'est carrément plus chiant de le faire en programmation qu'avec le storyboard (du coup, quand j'aurai plus de temps, je reviendrai dessus pour le refaire mieux et je pense d'ailleurs que ça sera plus propre..)


     


    Du coup, je me suis débloqué en faisant comme ça :


     


    Dans ma classe de base, j'ai rajouté :



    override func viewDidLoad() {

    ...
    myCollectionViewMessage?.register(ChatLogMessageCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")

    }


    Dans la classe de ma cellule (ChatLogMessageCollectionViewCell) :


    J'ai rajouté la fonction init() où j'ai mis le setupViews() dedans.. j'avais oublié de mettre ça, c'est pour ça que j'avais jamais de cellule avec les messages de créé..



    import UIKit

    class ChatLogMessageCollectionViewCell: UICollectionViewCell {

    let expediteurTextView: UITextView = {
    let textView = UITextView()
    textView.font = UIFont.boldSystemFont(ofSize: 16)
    textView.text = "Expéditeur"
    textView.backgroundColor = UIColor.clear
    return textView
    }()

    let messageTextView: UITextView = {
    let textView = UITextView()
    textView.font = UIFont.systemFont(ofSize: 16)
    textView.text = "Message d'exemple !"
    textView.backgroundColor = UIColor.clear
    return textView
    }()

    let bubbleTextView: UIView = {
    let view = UIView()
    view.layer.masksToBounds = true
    return view
    }()

    static let grayBubbleImage = UIImage(named: "bubblegray")!.resizableImage(withCapInsets: UIEdgeInsets(top: 22,left: 28,bottom: 22,right: 26)).withRenderingMode(.alwaysTemplate)
    static let blueBubbleImage = UIImage(named: "bubbleblue")!.resizableImage(withCapInsets: UIEdgeInsets(top: 22,left: 28,bottom: 22,right: 26)).withRenderingMode(.alwaysTemplate)

    let bubbleImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.image = ChatLogMessageCollectionViewCell.grayBubbleImage
    imageView.tintColor = UIColor(white: 0.80, alpha: 1)
    return imageView
    }()


    func setupViews() {
    addSubview(bubbleTextView)
    addSubview(expediteurTextView)
    addSubview(messageTextView)

    bubbleTextView.addSubview(bubbleImageView)
    bubbleTextView.ajouteContrainte(format: "H:|[v0]|", views: bubbleImageView)
    bubbleTextView.ajouteContrainte(format: "V:|[v0]|", views: bubbleImageView)
    }

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

    setupViews()
    }

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

    Ce post m'a aussi aidé : http://stackoverflow.com/questions/39438803/how-to-create-uicollectionviewcell-programmatically


     


    Et au final, mon problème est résolu, le dernier message ne passe plus sous la barre de texte ^^


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Je t'en prie !


     


    Mais tu as fait une petite bêtise quand-même  ::)


     


    Tu ne devrais pas mettre le code pour créer les vues dans init(frame:_) parce qu'elle n'est pas appelé si la cellule est créée d'un storyboard ; viewDidLoad est le bon endroit.


     


    Tu dois aussi considerer que les cellules sont réutilisées ; du coup, le même code pourrait être appelé plusieurs fois, en mettant plus de vues sur les précédentes, du coup, avalant plus de memoire.


     


    C'est pour ça que je te conseillerais à  nouveau de créer les cellules dans le storyboard.

  • InsouInsou Membre
    Bah quand j'ai mis le code dans viewDidLoad, ça ne fonctionnait pas, je passais même pas dedans O_o

    Du coup j'ai testé une autre alternative ^^

    Mais c'est clair qu'a terme, va falloir que je le refasse via le storyboard :)
  • Storyboard, c'est le Bien !  <3 </p>

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