webentwicklung-frage-antwort-db.com.de

Swift - Wie erstelle ich eine benutzerdefinierte viewForHeaderInSection-Datei mithilfe einer XIB-Datei?

Ich kann einfache benutzerdefinierte viewForHeaderInSection programmgesteuert wie folgt erstellen. Aber ich möchte viel komplexere Dinge tun, vielleicht die Verbindung mit einer anderen Klasse und deren Eigenschaften wie eine tableView-Zelle erreichen. Ich möchte einfach sehen, was ich tue.

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
}

Gibt es jemanden, der erklärt, wie ich mit xib eine benutzerdefinierte tableView-Header-Ansicht erstellen kann? Ich bin auf alte Obj-C-Themen gestoßen, aber ich bin neu in Swift language. Wenn jemand so ausführlich erklären würde, wäre es großartig.

1. Problem: Button @IBAction verbindet sich nicht mit meinem ViewController. (Fixed)

Mit dem Besitzer der Datei, ViewController-Basisklasse, behoben (linkes Umrissmenü angeklickt.)

2. Problem: Problem mit der Headerhöhe (Behoben)

Das Hinzufügen von headerView.clipsToBounds = true in viewForHeaderInSection : Methode wurde behoben.

Für Einschränkungswarnungen diese Antwort löste meine Probleme :

Wenn ich ImageView mit dieser Methode in viewController hinzugefügt habe, fließt es über tableView-Zeilen sieht aus wie Bild .

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

Wenn ich "automaticAdjustsScrollViewInsets" in "viewDidLoad" verwende, fließt das Bild in diesem Fall unter "navigationBar". -Fest-

self.automaticallyAdjustsScrollViewInsets = false

3. Ausgabe: Wenn Schaltfläche unter Ansicht (Behoben)

@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

Der typische Prozess für NIB-basierte Header wäre:

  1. Erstellen Sie eine Unterklasse UITableViewHeaderFooterView mit mindestens einem Ausgang für Ihr Etikett. Möglicherweise möchten Sie ihm auch eine Kennung geben, mit der Sie zurückentwickeln können, welchem ​​Abschnitt dieser Header entspricht. Ebenso möchten Sie möglicherweise ein Protokoll angeben, mit dem der Header den View-Controller über Ereignisse informieren kann (z. B. über das Tippen auf die Schaltfläche). In Swift 3 und höher:

    // 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. NIB erstellen. Persönlich gebe ich der NIB denselben Namen wie der Basisklasse, um die Verwaltung meiner Dateien in meinem Projekt zu vereinfachen und Verwirrung zu vermeiden. Wie auch immer, die wichtigsten Schritte umfassen:

    • Erstellen Sie eine Ansichts-NIB, oder fügen Sie der NIB eine Ansicht hinzu, wenn Sie mit einer leeren NIB begonnen haben.

    • Stellen Sie die Basisklasse der Ansicht so ein, dass sie Ihre Unterklasse UITableViewHeaderFooterView ist (in meinem Beispiel CustomHeader).

    • Fügen Sie Ihre Steuerelemente und Einschränkungen in IB hinzu.

    • Schließen Sie @IBOutlet - Verweise auf Verkaufsstellen in Ihrem Swift Code an;

    • Verbinden Sie den Button mit dem @IBAction; und

    • Stellen Sie für die Stammansicht in der NIB sicher, dass die Hintergrundfarbe auf "Standard" eingestellt ist, da Sie sonst ärgerliche Warnungen erhalten, wenn Sie die Hintergrundfarben ändern.

  3. Registrieren Sie die NIB in der viewDidLoad im View-Controller. In Swift 3 und höher:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
    }
    
  4. Deaktivieren Sie in viewForHeaderInSection eine wiederverwendbare Ansicht mit demselben Bezeichner, den Sie im vorherigen Schritt angegeben haben. Nachdem Sie dies getan haben, können Sie jetzt Ihren Ausgang verwenden und müssen nichts mehr mit programmgesteuert erstellten Einschränkungen usw. tun. Sie müssen lediglich den Delegaten angeben (damit das Protokoll für die Schaltfläche funktioniert). Zum Beispiel in 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. Wenn Sie den View-Controller als delegate für die Schaltfläche in der Header-Ansicht angeben möchten, müssen Sie natürlich dieses Protokoll einhalten:

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

Das klingt verwirrend, wenn ich alle Schritte aufführe, aber es ist wirklich ganz einfach, wenn Sie es einmal oder zweimal gemacht haben. Ich denke, es ist einfacher als die Header-Ansicht programmgesteuert zu erstellen.


In Matts Antwort protestiert er:

Das Problem ist ganz einfach, dass Sie eine UIView in einer Schreibfeder nicht auf magische Weise in eine UITableViewHeaderFooterView verwandeln können, indem Sie dies lediglich im Identitätsinspektor deklarieren.

Das ist einfach nicht richtig. Wenn Sie den obigen NIB-basierten Ansatz verwenden, ist die für die Stammansicht dieser Headeransicht instanziierte Klasse ist eine UITableViewHeaderFooterView-Unterklasse, keine UIView. Es instanziiert jede Klasse, die Sie für die Basisklasse für die Stammansicht der NIBs angeben.

Richtig ist jedoch, dass einige der Eigenschaften für diese Klasse (insbesondere contentView ) in diesem NIB-basierten Ansatz nicht verwendet werden. Es sollte wirklich eine optionale Eigenschaft sein, genau wie textLabel und detailTextLabel (oder, besser, sie sollten eine angemessene Unterstützung für hinzufügen UITableViewHeaderFooterView in IB). Ich bin damit einverstanden, dass dies ein schlechtes Design von Apple ist, aber es scheint mir ein schlampiges, eigenwilliges Detail zu sein, aber ein kleines Problem angesichts aller Probleme in Tabellenansichten. Zum Beispiel ist es außergewöhnlich, dass wir nach all den Jahren noch immer keine Prototypen von Kopf-/Fußzeilenansichten in Storyboards erstellen können und überhaupt auf diese NIB- und Klassenregistrierungstechniken angewiesen sind.

Es ist jedoch falsch zu folgern, dass man register(_:forHeaderFooterViewReuseIdentifier:) nicht verwenden kann, eine API-Methode, die seit iOS 6 aktiv ist. Lassen Sie uns das Baby nicht mit dem Badewasser herauswerfen.


Siehe vorherige Überarbeitung dieser Antwort für Swift 2 Wiedergaben.

81
Rob

Robs Antwort, obwohl sie überzeugend klingt und die Zeit überdauert hat, ist falsch und hat es immer getan. Es ist schwierig, alleine gegen die überwältigende "Weisheit" der Akzeptanz und gegen zahlreiche positive Stimmen zu bestehen, aber ich werde versuchen, den Mut zu fassen, die Wahrheit zu sagen.

Das Problem besteht ganz einfach darin, dass Sie eine UIView in einer Schreibspitze nicht auf magische Weise in eine UITableViewHeaderFooterView umwandeln können, indem Sie dies lediglich im Identitätsinspektor deklarieren. Ein UITableViewHeaderFooterView verfügt über wichtige Funktionen, die für den ordnungsgemäßen Betrieb von entscheidender Bedeutung sind, und ein einfaches UIView, egal wie Sie es mögen cast es fehlt ihnen.

  • Ein UITableViewHeaderFooterView hat ein contentView , und alle Ihre benutzerdefinierten Unteransichten müssen diesem hinzugefügt werden, nicht dem UITableViewHeaderFooterView.

    Bei einer UIView, die auf mysteriöse Weise als UITableViewHeaderFooterView dargestellt wird, fehlt dieses contentView in der Schreibfeder. Wenn Rob also "Fügen Sie Ihre Steuerelemente und Einschränkungen in IB hinzu" sagt, müssen Sie Unteransichten hinzufügen direkt zu UITableViewHeaderFooterView und nicht zu seinem contentView. Der Header ist somit falsch konfiguriert.

  • Ein weiteres Anzeichen für das Problem ist, dass Sie einer UITableViewHeaderFooterView keine Hintergrundfarbe zuweisen dürfen. In diesem Fall wird in der Konsole die folgende Meldung angezeigt:

    Das Festlegen der Hintergrundfarbe für UITableViewHeaderFooterView ist veraltet. Legen Sie stattdessen eine benutzerdefinierte UIView mit der gewünschten Hintergrundfarbe für die backgroundView-Eigenschaft fest.

    In der Schreibfeder können Sie jedoch nicht help eine Hintergrundfarbe für Ihre UITableViewHeaderFooterView festlegen, und Sie do erhalten diese Meldung in der Konsole.

Also, was ist die richtige Antwort auf die Frage? Es gibt keine mögliche Antwort. Apple hat hier große Fortschritte gemacht. Sie haben eine Methode bereitgestellt, mit der Sie eine Schreibfeder als Quelle für Ihre UITableViewHeaderFooterView registrieren können, aber das Objekt enthält keine UITableViewHeaderFooterView Bibliothek . Daher ist diese Methode nutzlos . Es ist unmöglich , eine UITableViewHeaderFooterView korrekt in einer Schreibfeder zu entwerfen.

Dies ist ein großer Fehler in Xcode. Ich habe einen Fehlerbericht zu diesem Thema eingereicht in 2013 und er steht noch offen. Ich repariere den Fehler Jahr für Jahr, und Apple schiebt immer wieder zurück und sagt: "Es wurde nicht bestimmt, wie oder wann das Problem behoben wird." Also erkennen sie den Fehler an, aber sie tun nichts darüber.

Was Sie können jedoch tun, ist das Entwerfen einer normalen UIView in der Schreibspitze und anschließend das manuelle Laden der View im Code (in Ihrer Implementierung von viewForHeaderInSection) von der Feder und stopfe es in die contentView deiner Header-Ansicht.

Nehmen wir zum Beispiel an, wir möchten unseren Header in der Schreibfeder entwerfen und wir haben eine Beschriftung im Header, an die wir einen Ausgang lab anschließen möchten. Dann brauchen wir sowohl eine benutzerdefinierte Header-Klasse als auch eine benutzerdefinierte View-Klasse:

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

Wir registrieren die class unserer Header-Ansicht, nicht die Schreibfeder:

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

In der Datei view xib deklarieren wir unsere Ansicht als MyHeaderViewContent - not MyHeaderView.

In viewForHeaderInSection pflücken wir die Ansicht aus der Schreibfeder, fügen sie in das contentView des Headers ein und konfigurieren den Verweis darauf:

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
}

Es ist schrecklich und nervig, aber es ist das Beste, was Sie tun können.

31
matt

Ich habe nicht genug Ruf, um Matt einen Kommentar zukommen zu lassen.

Hier fehlt nur noch, dass alle Unteransichten aus UITableViewHeaderFooterView.contentView entfernt werden, bevor neue Ansichten hinzugefügt werden. Dadurch wird die wiederverwendete Zelle in den Ausgangszustand zurückgesetzt und ein Speicherverlust vermieden.

2
copied