web-dev-qa-db-fra.com

Définir la propriété de vue UIViewController sur la classe UIView personnalisée sans storyboard/nib

J'ai une UIViewController appelée LoginViewController. Je souhaite construire la vue de cette LoginViewController entièrement par programmation dans une classe UIView personnalisée appelée LoginView au lieu de créer tous les éléments de ma LoginViewController. De cette façon, j'empêche le code "View" dans une classe de contrôleur (MVC). 

Dans le code ci-dessous, je règle l'affichage de ma LoginViewController sur ma LoginView qui, par souci de simplicité, ne contient que 2 UILabels

class LoginViewController: UIViewController {

override func loadView() {
    super.loadView()

    self.view = LoginView(frame: CGRect.zero)
}

La classe LoginView initialise les deux étiquettes et devrait définir certaines contraintes.

class LoginView: UIView {

var usernameLabel: UILabel!
var passwordLabel: UILabel!


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

    setupLabels()
}

convenience init () {
    self.init(frame:CGRect.zero)
}

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

private func setupLabels(){
    //Init labels and set a simple text
    self.usernameLabel = UILabel()
    self.usernameLabel.text = "Username"
    self.passwordLabel = UILabel()
    self.passwordLabel.text = "Password"

    //Set constraints which aren't possible since there is no contentView, perhaps using the frame?
    }
}

Cela ne fonctionne pas car les limites de la vue sont égales à 0. Cependant, comme je ne pouvais trouver aucune ressource permettant de savoir si cela était possible, j'ai donc essayé mon approche qui ne fonctionnait pas. 

Comment définir la vue d'un UIViewController sur un UIView personnalisé créé par programme? Ou l'extrait ci-dessus est-il recommandé?

Voici la solution de travail basée sur la réponse de Jadar:

class LoginViewController: UIViewController {

    override func loadView() {
        view = LoginView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()        
    //  Do any additional setup after loading the view.

    }
}

class LoginView: UIView {

var usernameLabel: UILabel!
var passwordLabel: UILabel!

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

    self.usernameLabel = UILabel()
    self.usernameLabel.text = "Username"
    self.passwordLabel = UILabel()
    self.passwordLabel.text = "Password"

    addSubview(usernameLabel)
    addSubview(passwordLabel)

    if let superview = usernameLabel.superview{
        //Setting AutoLayout using SnapKit framework
        usernameLabel.snp.makeConstraints { (make) in
            make.center.equalTo(superview)
        }
    }
}

Résultat:

 Result

8
Hapeki

On dirait qu'il y a vraiment deux questions ici. Premièrement, quel est le meilleur moyen de configurer un ViewController par programme. L'autre, comment configurer une vue par programme. 

Tout d'abord, la meilleure façon pour un ViewController d'utiliser une sous-classe UIView différente par programme consiste à l'initialiser et à l'affecter dans la méthode loadView. Par Apple docs :

Vous pouvez remplacer cette méthode afin de créer vos vues manuellement. Si vous le souhaitez, affectez la vue racine de votre hiérarchie de vues à La propriété de vue. Les vues que vous créez doivent être des instances uniques et Ne doit pas être partagé avec un autre objet du contrôleur de vue. Votre implémentation Personnalisée de cette méthode ne doit pas appeler super.

Cela ressemblerait à quelque chose comme ça:

class LoginViewController: UIViewController {    
    override func loadView() {
        // Do not call super!
        view = LoginView()
    }
}

De cette façon, vous ne devriez pas avoir à gérer le dimensionnement, car le View Controller lui-même devrait s'en occuper (comme il le fait avec son propre UIView).

Rappelez-vous, n'appelez pas super.loadView() ou le contrôleur sera confus. De plus, la première fois que j'ai essayé cela, j'ai eu un écran noir parce que j'ai oublié d'appeler window.makeKeyAndVisible() dans mon délégué d'application. Dans ce cas, la vue n'a même jamais été ajoutée à la hiérarchie des fenêtres. Vous pouvez toujours utiliser le bouton View Introspecter dans Xcode pour voir ce qui se passe.

Deuxièmement, vous devrez appeler self.addSubview(_:) dans votre sous-classe UIView pour les afficher. Une fois que vous les avez ajoutés en tant que sous-vues, vous pouvez ajouter des contraintes avec NSLayoutConstraint

private func setupLabels(){
    // Initialize labels and set their text
    usernameLabel = UILabel()
    usernameLabel.text = "Username"
    usernameLabel.translatesAutoresizingMaskIntoConstraints = false // Necessary because this view wasn't instantiated by IB
    addSubview(usernameLabel)

    passwordLabel = UILabel()
    passwordLabel.text = "Password"
    passwordLabel.translatesAutoresizingMaskIntoConstraints = false // Necessary because this view wasn't instantiated by IB
    addSubview(passwordLabel)

    NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-10-[view]", options: [], metrics: nil, views: ["view":usernameLabel]))
    NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|-20-[view]", options: [], metrics: nil, views: ["view":passwordLabel]))

    NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-10-[view]", options: [], metrics: nil, views: ["view":usernameLabel]))
    NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|-20-[view]", options: [], metrics: nil, views: ["view":passwordLabel]))
}

Pour plus d'informations sur le langage de format visuel utilisé pour créer les contraintes, voir le Guide VFL

9
Jadar

Redéfinissez la méthode layoutSubviews pour mettre à jour les cadres des sous-vues dans votre vue personnalisée. 

Et ne jamais appeler super.loadView(). Ceci est documenté pour la méthode loadView

2
rmaddy

Vous devez charger la vue personnalisée lorsque les contraintes de présentation de LoginViewController sont déjà chargées. Essayez ceci: 

  override func viewDidLoad() {
      super.viewDidLoad()
      let newView = LoginView(frame: view.bounds)
      view.addSubview(newView)
    }
2
DariusV

Dans la méthode loadView de votre Viewcontroller, procédez comme suit:

class LoginViewController: UIViewController {
    override func loadView() {
        super.view = LoginView()
    }
}

Dans la classe personnalisée de votre UIView, procédez comme suit: 

class LoginView: UIView {
    convenience init() {
        self.init(frame: UIScreen.main.bounds)
        setupLabels()
    }
}

Maintenant, votre UIView a un cadre, et vous pouvez configurer toutes vos vues avec du code en leur fournissant des cadres.

0
Ashish