web-dev-qa-db-fra.com

Swift - Comment créer viewForHeaderInSection personnalisé, en utilisant un fichier XIB?

Je peux créer un viewForHeaderInSection personnalisé simple dans un programme, comme ci-dessous. Mais je veux faire des choses beaucoup plus complexes, peut-être une connexion avec une classe différente et atteindre leurs propriétés comme une cellule tableView. Simplement, je veux voir ce que je fais.

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    if(section == 0) {

        let view = UIView() // The width will be the same as the cell, and the height should be set in tableView:heightForRowAtIndexPath:
        let label = UILabel()
        let button   = UIButton(type: UIButtonType.System)

        label.text="My Details"
        button.setTitle("Test Title", forState: .Normal)
        // button.addTarget(self, action: Selector("visibleRow:"), forControlEvents:.TouchUpInside)

        view.addSubview(label)
        view.addSubview(button)

        label.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false

        let views = ["label": label, "button": button, "view": view]

        let horizontallayoutContraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-10-[label]-60-[button]-10-|", options: .AlignAllCenterY, metrics: nil, views: views)
        view.addConstraints(horizontallayoutContraints)

        let verticalLayoutContraint = NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0)
        view.addConstraint(verticalLayoutContraint)

        return view
    }

    return nil
}


func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 50
}

Quelqu'un peut-il expliquer comment créer une vue d'en-tête tableView personnalisée à l'aide de xib? J'ai rencontré d'anciens sujets Obj-C mais je suis nouveau avec la langue Swift. Si quelqu'un explique en détail, ce serait génial.

1.issue: Le bouton @IBAction ne se connecte pas à mon ViewController. (Fixe)

Résolu avec le propriétaire du fichier, classe de base ViewController (clic sur le menu de gauche).

2.issue: Problème de hauteur d'en-tête (corrigé)

Résolution de l'ajout de headerView.clipsToBounds = true dans viewForHeaderInSection : méthode.

Pour les avertissements de contrainte cette réponse a résolu mes problèmes :

Lorsque j'ai ajouté la même contrainte de hauteur à ImageView avec cette méthode dans viewController, celle-ci s'écoule sur les lignes tableView ressemblent à l'image .

 func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 120
}

Si j'utilise, automatiquementAdjustsScrollViewInsets dans viewDidLoad, dans ce cas, l'image se trouve sous navigationBar. -fixé-

self.automaticallyAdjustsScrollViewInsets = false

3.issue: Si le bouton est sous View (Fixé)

@IBAction func didTapButton(sender: AnyObject) {
    print("tapped")

    if let upView = sender.superview {
        if let headerView = upView?.superview as? CustomHeader {
            print("in section \(headerView.sectionNumber)")
        }

    }
}
33
burakgunduz

Le processus typique pour les en-têtes basés sur NIB serait:

  1. Créez la sous-classe UITableViewHeaderFooterView avec au moins un point de vente pour votre étiquette. Vous voudrez peut-être aussi lui donner un identifiant grâce auquel vous pourrez procéder à un reverse engineering à la section à laquelle correspond cet en-tête. De même, vous souhaiterez peut-être spécifier un protocole permettant à l'en-tête d'informer le contrôleur de la vue d'événements (comme l'activation du bouton). Ainsi, dans Swift 3 et versions ultérieures:

    // if you want your header to be able to inform view controller of key events, create protocol
    
    protocol CustomHeaderDelegate: class {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int)
    }
    
    // define CustomHeader class with necessary `delegate`, `@IBOutlet` and `@IBAction`:
    
    class CustomHeader: UITableViewHeaderFooterView {
        static let reuseIdentifier = "CustomHeader"
    
        weak var delegate: CustomHeaderDelegate?
    
        @IBOutlet weak var customLabel: UILabel!
    
        var sectionNumber: Int!  // you don't have to do this, but it can be useful to have reference back to the section number so that when you tap on a button, you know which section you came from; obviously this is problematic if you insert/delete sections after the table is loaded; always reload in that case
    
        @IBAction func didTapButton(_ sender: AnyObject) {
            delegate?.customHeader(self, didTapButtonInSection: section)
        }
    
    }
    
  2. Créer une NIB. Personnellement, je donne à la NIB le même nom que la classe de base pour simplifier la gestion de mes fichiers dans mon projet et éviter toute confusion. Quoi qu'il en soit, les étapes clés comprennent:

    • Créez une vue NIB ou, si vous avez commencé avec une NIB vide, ajoutez une vue à la NIB;

    • Définissez la classe de base de la vue comme quelle que soit votre sous-classe UITableViewHeaderFooterView (dans mon exemple, CustomHeader);

    • Ajoutez vos contrôles et contraintes dans IB;

    • Raccordez les références @IBOutlet Aux prises dans votre Swift);

    • Reliez le bouton au @IBAction; et

    • Pour la vue racine de la carte NIB, assurez-vous de définir la couleur d'arrière-plan sur "par défaut", sinon vous recevrez des avertissements gênants sur la modification des couleurs d'arrière-plan.

  3. Dans le viewDidLoad du contrôleur de vue, enregistrez le NIB. Dans Swift 3 et versions ultérieures:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
    }
    
  4. Dans viewForHeaderInSection, supprimez la mise en file d'attente d'une vue réutilisable à l'aide du même identifiant que celui que vous avez spécifié à l'étape précédente. Cela fait, vous pouvez maintenant utiliser votre sortie, vous n'avez rien à faire avec les contraintes créées par programme, etc. Le seul que vous ayez à faire (pour que le protocole fonctionne), est de spécifier son délégué. Par exemple, dans Swift 3:

    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "CustomHeader") as! CustomHeader
    
        headerView.customLabel.text = content[section].name  // set this however is appropriate for your app's model
        headerView.sectionNumber = section
        headerView.delegate = self
    
        return headerView
    }
    
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44  // or whatever
    }
    
  5. Évidemment, si vous spécifiez le contrôleur de vue comme delegate pour le bouton dans la vue d'en-tête, vous devez vous conformer à ce protocole:

    extension ViewController: CustomHeaderDelegate {
        func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
            print("did tap button", section)
        }
    }
    

Tout cela semble déroutant lorsque je liste toutes les étapes impliquées, mais c'est vraiment très simple une fois que vous l'avez fait une ou deux fois. Je pense que c'est plus simple que de construire la vue d'en-tête par programme.


Dans réponse de matt , il proteste:

Le problème, tout simplement, est que vous ne pouvez pas transformer par magie un UIView d’une plume en un UITableViewHeaderFooterView simplement en le déclarant dans l’inspecteur d’identité.

Ce n'est tout simplement pas correct. Si vous utilisez l'approche NIB ci-dessus, la classe instanciée pour la vue racine de cette vue d'en-tête est une sous-classe UITableViewHeaderFooterView. , pas un UIView. Il instancie la classe que vous spécifiez pour la classe de base pour la vue racine de la carte NIB.

Ce qui est correct, cependant, est que certaines des propriétés de cette classe (notamment le contentView ) ne sont pas utilisées dans cette approche basée sur la carte NIB. Ce devrait vraiment être une propriété optionnelle, comme textLabel et detailTextLabel sont (ou, mieux, ils devraient ajouter le support approprié pour UITableViewHeaderFooterView dans IB). Je conviens que la conception d'Apple est médiocre, mais cela me semble être un détail bâclé et particulier, mais un problème mineur compte tenu de tous les problèmes rencontrés dans les vues de tableau. Par exemple, il est extraordinaire qu'après toutes ces années, nous ne puissions toujours pas créer de vues de prototype d'en-tête/de pied de page dans les scénarimages et nous devons compter sur ces techniques d'inscription de classe et de NIB.

Cependant, il est incorrect de conclure que l’on ne peut pas utiliser register(_:forHeaderFooterViewReuseIdentifier:) , une méthode API utilisée activement depuis iOS 6. Ne jetons pas le bébé avec l’eau du bain.


Voir révision précédente de cette réponse pour Swift 2 rendus.

81
Rob

La réponse de Rob, bien que cela semble convaincant et a résisté à l'épreuve du temps, est fausse et l'a toujours été. Il est difficile de rester seul face à la "sagesse" de l'acceptation de la foule et à de nombreux votes favorables, mais je vais essayer de rassembler le courage de dire la vérité.

Le problème, tout simplement, est que vous ne pouvez pas transformer par magie une UIView d’une plume en une UITableViewHeaderFooterView en la déclarant simplement dans l’inspecteur d’identité. UITableViewHeaderFooterView a des fonctionnalités importantes qui sont essentielles à son bon fonctionnement, et un UIView simple, peu importe comment vous pouvez cast, il les manque.

  • UITableViewHeaderFooterView a un contentView , et toutes vos sous-vues personnalisées doivent y être ajoutées, et non à UITableViewHeaderFooterView.

    Mais une UIView exprimée mystérieusement en tant que UITableViewHeaderFooterView n'a pas cette contentView dans la nib. Ainsi, lorsque Rob dit "Ajoutez vos contrôles et vos contraintes dans IB", il vous fait ajouter des sous-vues directement sur UITableViewHeaderFooterView, et not ​​sur ses contentView . L'en-tête est donc mal configuré.

  • Un autre signe du problème est que vous n'êtes pas autorisé à attribuer une couleur d'arrière-plan à UITableViewHeaderFooterView. Si vous le faites, vous aurez ce message dans la console:

    La définition de la couleur d'arrière-plan sur UITableViewHeaderFooterView est obsolète. Veuillez définir un UIView personnalisé avec la couleur d'arrière-plan de votre choix sur la propriété backgroundView.

    Mais dans le nib, vous ne pouvez pas help définir une couleur d'arrière-plan sur votre UITableViewHeaderFooterView, et vous faire obtenir ce message dans la console.

Alors, quelle est la bonne réponse à la question? Il y a pas possible réponse. Apple a énormément de succès ici. Ils ont fourni une méthode qui vous permet d'enregistrer un nib en tant que source de votre UITableViewHeaderFooterView, mais il y a pas de UITableViewHeaderFooterView dans la bibliothèque d'objets. Par conséquent, cette méthode est inutilisable. Il est impossible de concevoir correctement un UITableViewHeaderFooterView dans un nib.

C'est un énorme bug dans Xcode. J'ai déposé un rapport de bogue sur cette question en 201 et il est toujours assis, ouvert. Je refile le bogue année après année et Apple n'arrête pas de repousser en disant "on n'a pas encore déterminé comment ni quand le problème sera résolu." Alors ils reconnaissent le bogue, mais ils ne font rien. à propos de ça.

Ce que vous pouvez faites, cependant, est de concevoir un UIView normal dans le nib, puis dans le code (dans votre implémentation de viewForHeaderInSection), chargez la vue manuellement à partir du nib, etc. dans la contentView de votre vue d’en-tête.

Par exemple, supposons que nous voulions concevoir notre en-tête dans la plume et que nous ayons une étiquette dans l'en-tête à laquelle nous voulons connecter une prise lab. Ensuite, nous avons besoin à la fois d'une classe d'en-tête personnalisée et d'une classe de vue personnalisée:

class MyHeaderView : UITableViewHeaderFooterView {
    weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
    @IBOutlet weak var lab : UILabel!
}

Nous enregistrons la classe class de notre en-tête, pas le nib:

self.tableView.register(MyHeaderView.self,
    forHeaderFooterViewReuseIdentifier: self.headerID)

Dans le fichier view xib, nous déclarons que notre vue est un MyHeaderViewContent - not ​​un MyHeaderView.

Dans viewForHeaderInSection, nous extrayons la vue du nib, l'enfouissons dans le contentView de l'en-tête et nous configurons la référence correspondante:

override func tableView(_ tableView: UITableView, 
    viewForHeaderInSection section: Int) -> UIView? {
    let h = tableView.dequeueReusableHeaderFooterView(
        withIdentifier: self.headerID) as! MyHeaderView
    if h.content == nil {
        let v = UINib(nibName: "MyHeaderView", bundle: nil).instantiate
            (withOwner: nil, options: nil)[0] as! MyHeaderViewContent
        h.contentView.addSubview(v)
        v.translatesAutoresizingMaskIntoConstraints = false
        v.topAnchor.constraint(equalTo: h.contentView.topAnchor).isActive = true
        v.bottomAnchor.constraint(equalTo: h.contentView.bottomAnchor).isActive = true
        v.leadingAnchor.constraint(equalTo: h.contentView.leadingAnchor).isActive = true
        v.trailingAnchor.constraint(equalTo: h.contentView.trailingAnchor).isActive = true
        h.content = v
        // other initializations for all headers go here
    }
    h.content.lab.text = // whatever
    // other initializations for this header go here
    return h
}

C'est affreux et énervant, mais c'est le mieux que vous puissiez faire.

31
matt

Je n'ai pas assez de réputation pour ajouter un commentaire à la réponse de Matt.

Quoi qu'il en soit, la seule chose qui manque ici est de supprimer toutes les sous-vues de UITableViewHeaderFooterView.contentView avant d'ajouter de nouvelles vues. Cela réinitialisera la cellule réutilisée à son état initial et évitera une fuite de mémoire.

2
copied