Bonne pratique pour sous-classer UIView?

158

Je travaille sur des contrôles d'entrée personnalisés basés sur UIView et j'essaie de vérifier les bonnes pratiques pour configurer la vue. Lorsque vous travaillez avec un UIViewController, il est assez simple d'utiliser les loadViewet connexes viewWill, des viewDidméthodes, mais quand un sous - classement UIView, les plus proches methosds que j'ai sont `awakeFromNib, drawRectet layoutSubviews. (Je pense en termes de configuration et de rappel de démontage.) Dans mon cas, je configure mon cadre et mes vues internes dans layoutSubviews, mais je ne vois rien à l'écran.

Quelle est la meilleure façon de m'assurer que ma vue a la hauteur et la largeur correctes que je souhaite avoir? (Ma question s'applique indépendamment du fait que j'utilise la mise en page automatique, bien qu'il puisse y avoir deux réponses.) Quelle est la «meilleure pratique» appropriée?

Moshe
la source

Réponses:

298

Apple a défini assez clairement comment sous-classer UIViewdans la doc.

Consultez la liste ci-dessous, en particulier jetez un œil à initWithFrame:et layoutSubviews. Le premier est destiné à configurer le cadre de votre UIViewalors que le second est destiné à configurer le cadre et la disposition de ses sous-vues.

Souvenez-vous également qu'il initWithFrame:n'est appelé que si vous instanciez votre UIViewprogramme par programme. Si vous le chargez à partir d'un fichier nib (ou d'un storyboard), initWithCoder:sera utilisé. Et dans initWithCoder:le cadre n'a pas encore été calculé, vous ne pouvez donc pas modifier le cadre que vous avez configuré dans Interface Builder. Comme suggéré dans cette réponse, vous pouvez penser à appeler initWithFrame:depuis initWithCoder:pour configurer la trame.

Enfin, si vous chargez votre à UIViewpartir d'une pointe (ou d'un storyboard), vous avez également la awakeFromNibpossibilité d'effectuer des initialisations personnalisées de cadre et de mise en page, car quand il awakeFromNibest appelé, il est garanti que chaque vue de la hiérarchie a été désarchivée et initialisée.

Extrait du doc ​​de NSNibAwaking(maintenant remplacé par le doc de awakeFromNib):

Les messages à d'autres objets peuvent être envoyés en toute sécurité à partir de awakeFromNib - à ce moment, il est assuré que tous les objets sont désarchivés et initialisés (mais pas nécessairement réveillés, bien sûr)

Il est également intéressant de noter qu'avec la mise en page automatique, vous ne devez pas définir explicitement le cadre de votre vue. Au lieu de cela, vous êtes censé spécifier un ensemble de contraintes suffisantes pour que le cadre soit automatiquement calculé par le moteur de mise en page.

Directement à partir de la documentation :

Méthodes de remplacement

Initialisation

  • initWithFrame:Il est recommandé de mettre en œuvre cette méthode. Vous pouvez également implémenter des méthodes d'initialisation personnalisées en plus ou à la place de cette méthode.

  • initWithCoder: Implémentez cette méthode si vous chargez votre vue à partir d'un fichier nib Interface Builder et que votre vue nécessite une initialisation personnalisée.

  • layerClassImplémentez cette méthode uniquement si vous souhaitez que votre vue utilise une couche Core Animation différente pour son magasin de stockage. Par exemple, si vous utilisez OpenGL ES pour faire votre dessin, vous voudrez remplacer cette méthode et renvoyer la classe CAEAGLLayer.

Dessin et impression

  • drawRect:Implémentez cette méthode si votre vue dessine un contenu personnalisé. Si votre vue ne fait aucun dessin personnalisé, évitez de remplacer cette méthode.

  • drawRect:forViewPrintFormatter: N'implémentez cette méthode que si vous souhaitez dessiner le contenu de votre vue différemment lors de l'impression.

Contraintes

  • requiresConstraintBasedLayout Implémentez cette méthode de classe si votre classe de vue nécessite des contraintes pour fonctionner correctement.

  • updateConstraints Implémentez cette méthode si votre vue doit créer des contraintes personnalisées entre vos sous-vues.

  • alignmentRectForFrame:, frameForAlignmentRect:Implémentez ces méthodes pour remplacer la façon dont vos vues sont alignées sur d'autres vues.

Disposition

  • sizeThatFits:Implémentez cette méthode si vous souhaitez que votre vue ait une taille par défaut différente de celle qu'elle aurait normalement lors des opérations de redimensionnement. Par exemple, vous pouvez utiliser cette méthode pour empêcher votre vue de se réduire au point où les sous-vues ne peuvent pas être affichées correctement.

  • layoutSubviews Implémentez cette méthode si vous avez besoin d'un contrôle plus précis sur la mise en page de vos sous-vues que ne le fournissent les comportements de contrainte ou de redimensionnement automatique.

  • didAddSubview:, willRemoveSubview:Implémentez ces méthodes si nécessaire pour suivre les ajouts et les suppressions de sous-vues.

  • willMoveToSuperview:, didMoveToSuperviewImplémentez ces méthodes si nécessaire pour suivre le mouvement de la vue actuelle dans votre hiérarchie de vues.

  • willMoveToWindow:, didMoveToWindowImplémentez ces méthodes si nécessaire pour suivre le mouvement de votre vue vers une autre fenêtre.

Gestion des événements:

  • touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent:Mettre en œuvre ces méthodes si vous avez besoin pour gérer les événements tactiles directement. (Pour la saisie basée sur les gestes, utilisez des outils de reconnaissance de gestes.)

  • gestureRecognizerShouldBegin: Implémentez cette méthode si votre vue gère directement les événements tactiles et peut vouloir empêcher les outils de reconnaissance de gestes attachés de déclencher des actions supplémentaires.

Gabriele Petronella
la source
qu'en est-il - (void) setFrame: (CGRect) frame?
pfrank
Eh bien, vous pouvez certainement le remplacer, mais dans quel but?
Gabriele Petronella
pour changer la mise en page / le dessin à chaque fois que la taille du cadre ou l'emplacement change
pfrank
1
Et quoi layoutSubviews?
Gabriele Petronella
De stackoverflow.com/questions/4000664/… , "le problème avec ceci est que les sous-vues peuvent non seulement changer leur taille, mais elles peuvent animer ce changement de taille. Lorsque UIView exécute l'animation, il n'appelle pas layoutSubviews à chaque fois." Je ne l'
ai
38

Cela revient toujours haut dans Google. Voici un exemple mis à jour pour swift.

La didLoadfonction vous permet de mettre tout votre code d'initialisation personnalisé. Comme d'autres l'ont mentionné, didLoadsera appelé lorsqu'une vue est créée par programme via init(frame:)ou lorsque le désérialiseur XIB fusionne un modèle XIB dans votre vue viainit(coder:)

À part : layoutSubviewset updateConstraintssont appelés plusieurs fois pour la majorité des vues. Ceci est destiné aux mises en page et aux ajustements avancés à passes multiples lorsque les limites d'une vue changent. Personnellement, j'évite les mises en page multi-passes lorsque cela est possible car elles brûlent les cycles du processeur et rendent tout un casse-tête. De plus, je mets du code de contrainte dans les initialiseurs eux-mêmes car je les invalide rarement.

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

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

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}
seo
la source
Pouvez-vous expliquer quand / pourquoi vous diviseriez le code de contrainte de mise en page entre une méthode appelée à partir de votre processus d'initialisation et layoutSubviews et updateConstraints? Il semble que ce soient les trois emplacements candidats possibles pour placer le code de mise en page. Alors, comment savoir quand / quoi / pourquoi diviser le code de mise en page entre les trois?
Clay Ellis
3
Je n'utilise jamais updateConstraints; updateConstraints peut être agréable car vous savez que votre hiérarchie de vues a été entièrement configurée dans init, vous ne pouvez donc pas lever d'exception en ajoutant une contrainte entre deux vues qui ne sont pas dans la hiérarchie :) layoutSubviews ne devrait jamais avoir de modifications de contrainte; cela peut facilement provoquer une récursion infinie car layoutSubviews est appelé si les contraintes sont «invalidées» pendant le passage de layout. La configuration manuelle de la mise en page (comme pour définir directement les cadres, ce que vous n'avez plus rarement besoin de faire, sauf pour des raisons de performances) va dans layoutSubviews. Personnellement, je place la création de contraintes dans init
seo
Pour le code de rendu personnalisé, devons-nous remplacer la drawméthode?
Petrus Theron
14

Il y a un résumé correct dans la documentation Apple , et cela est bien couvert dans le cours gratuit de Stanford disponible sur iTunes. Je présente ma version TL; DR ici:

Si votre classe se compose principalement de sous-vues, le bon endroit pour les allouer est dans les initméthodes. Pour les vues, il existe deux initméthodes différentes qui peuvent être appelées, selon que votre vue est instanciée à partir du code ou d'une pointe / storyboard. Ce que je fais, c'est d'écrire ma propre setupméthode, puis de l'appeler à partir des méthodes initWithFrame:et initWithCoder:.

Si vous effectuez un dessin personnalisé, vous souhaitez en effet remplacer drawRect:dans votre vue. Si votre vue personnalisée est principalement un conteneur pour les sous-vues, vous n'aurez probablement pas besoin de le faire.

Ne remplacez que layoutSubViewssi vous voulez faire quelque chose comme ajouter ou supprimer une sous-vue selon que vous êtes en orientation portrait ou paysage. Sinon, vous devriez pouvoir le laisser tranquille.

dpassage
la source
J'utilise votre réponse pour changer le cadre de la sous-vue de la vue (qui est awakeFromNib) dans layoutSubViews, cela a fonctionné.
avion
1

layoutSubviews est destiné à définir le cadre sur les vues enfants, pas sur la vue elle-même.

Pour UIView, le constructeur désigné est généralement initWithFrame:(CGRect)frameet vous devez définir le cadre ici (ou dans initWithCoder:), en ignorant éventuellement la valeur du cadre. Vous pouvez également fournir un constructeur différent et y définir le cadre.

proxi
la source
pourriez-vous prendre plus de détails? Je ne connaissais pas votre moyenne. Comment définir le cadre de la sous-vue d'une vue? la vue estawakeFromNib
avion
Avance rapide jusqu'en 2016, vous ne devriez probablement pas du tout définir de cadres et utiliser la mise en page automatique (contraintes). Si la vue provient de XIB (ou du storyboard), la sous-vue doit déjà être configurée.
proxi