Swift - jak stworzyć własny viewForHeaderInSection przy użyciu pliku XIB?


Mogę stworzyć prosty niestandardowy viewForHeaderInSection programowo, jak poniżej. Ale chcę zrobić znacznie bardziej złożone rzeczy, być może połączyć się z inną klasą i dotrzeć do ich właściwości, takich jak komórka tableView. Chcę tylko zobaczyć, co robię.
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]-|", 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
}

Czy jest ktoś, kto może wyjaśnić, jak mogę utworzyć niestandardowy widok nagłówka tableView przy użyciu xib? Natknąłem się na stare tematy Obj-C, ale jestem nowy w języku Swift. Byłoby wspaniale, gdyby ktokolwiek mógł wyjaśnić te same szczegóły.


1.issue:

Przycisk @IBAction nie łączy się z moim ViewController.

(Naprawiony

)

Rozwiązany z właścicielem pliku, klasą bazową ViewController (kliknięto menu konspektu po lewej stronie).


2.issue:

problem z wysokością nagłówka

(naprawiony

)

Postanowiłem dodać

headerView.clipsToBounds = true

w metodzie

viewForHeaderInSection

:.

Ostrzeżenia o ograniczeniach
ta odpowiedź rozwiązała moje problemy
https://coderoad.ru/11664115/
:
Kiedy dodałem ImageView nawet to samo ograniczenie wysokości przy użyciu tej metody w viewController, przepływa przez wiersze tableView,

podobny do obrazu
https://i.stack.imgur.com/yoYQ6.png
.
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 120
}

Jeśli używam automaticAdjustsScrollViewInsets w viewDidLoad, to w tym przypadku obraz przepływa pod navigationBar. - naprawiony-
self.automaticallyAdjustsScrollViewInsets = false

3.issue:

jeśli przycisk jest ukryty

(naprawiony

)

@IBAction func didTapButton(sender: AnyObject) {
print("tapped") if let upView = sender.superview {
if let headerView = upView?.superview as? CustomHeader {
print("in section \(headerView.sectionNumber)")
} }
}

Zaproszony:
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Typowy proces dla nagłówków opartych na NIB wyglądałby tak:
  • Podklasa
    UITableViewHeaderFooterView
    Z co najmniej wyjściem dla Twojej etykiety. Możesz również nadać mu jakiś identyfikator, za pomocą którego możesz odtworzyć inżynierię, której sekcji odpowiada ten tytuł. Podobnie można określić protokół, za pomocą którego nagłówek może informować kontroler widoku o zdarzeniach (takich jak kliknięcie przycisku). Tak więc w Swift 3 i nowszych:
    // if you want your header to be able to inform view controller of key events, create protocolprotocol 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) }}
  • Utwórz NIB. Osobiście nadaję NIB tę samą nazwę co klasie bazowej, aby ułatwić zarządzanie plikami w moim projekcie i uniknąć nieporozumień. Jeśli już, najważniejsze kroki obejmują: [list][*]Utwórz widok NIB lub, jeśli zacząłeś od pustego NIB, dodaj widok do NIB.;
  • Ustaw klasę widoku bazowego na podklasę
    UITableViewHeaderFooterView
    (w moim przykładzie
    CustomHeader
    );
  • Dodaj swoje kontrole i ograniczenia do IB;
  • Dołącz odniesienia do gniazd
    @IBOutlet
    w swoim kodzie Swift;
  • Połącz przycisk z
    @IBAction
    ; i
  • W widoku głównym w NIB upewnij się, że kolor tła jest ustawiony na „domyślny”, w przeciwnym razie otrzymasz irytujące ostrzeżenia o zmianie koloru tła.

[/*]
[*]
W
viewDidLoad
w kontrolerze widoku zarejestruj NIB. W wersji Swift 3 i nowszych:
override func viewDidLoad() {
super.viewDidLoad() tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
}

[/*]
[*]
W
viewForHeaderInSection
usuń z kolejki widok wielokrotnego użytku, używając tego samego identyfikatora, który został określony w poprzednim kroku. Po wykonaniu tej czynności możesz teraz korzystać z gniazda, nie musisz nic robić z programowo utworzonymi ograniczeniami itp. Jedyne, co musisz zrobić (aby protokół działał dla przycisku), to określić jego delegata. Na przykład w 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
}

[/*]
[*]
Oczywiście, jeśli zamierzasz określić kontroler widoku jako
delegata
dla przycisku w widoku tytułowym, musisz dostosować się do tego protokołu:
extension ViewController: CustomHeaderDelegate {
func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
print("did tap button", section)
}
}

[/*]
[/list]
To wszystko wydaje się zagmatwane, gdy wymieniam wszystkie niezbędne kroki, ale jest to całkiem proste, jeśli zrobiłeś to raz lub dwa razy. Myślę, że jest to łatwiejsze niż programowe tworzenie widoku nagłówka.
W

odpowiedz mat
https://stackoverflow.com/a/50087040/1271826
protesty:

Problem polega na tym, że nie można magicznie zmienić
UIView
na końcu w
UITableViewHeaderFooterView
, po prostu zadeklarując go jako taki w inspektorze tożsamości.

To po prostu nie jest w porządku. Jeśli korzystasz z powyższego podejścia opartego na NIB, to klasa, która jest tworzona dla widoku głównego tego widoku nagłówka, to

jest

podklasy
UITableViewHeaderFooterView
, a nie
UIView
. Tworzy instancję dowolnej klasy, którą określisz dla klasy bazowej dla widoku głównego NIB.
Jednak prawdą jest, że niektóre właściwości tej klasy (zwłaszcza
contentView
https://developer.apple.com/do ... ntview) nie są używane w tym podejściu opartym na NIB. To naprawdę powinna być opcjonalna właściwość, na przykład

i
https://developer.apple.com/do ... label
textLabel
i
detailTextLabel
https://developer.apple.com/do ... label
(lub lepiej, powinny dodać odpowiednią obsługę
UITableViewHeaderFooterView
w IB). Zgadzam się, że to zły projekt ze strony Apple, ale wydaje mi się, że jest to niechlujny, dziwaczny szczegół, ale drobny problem, biorąc pod uwagę wszystkie problemy w widokach tabel. Na przykład, to niesamowite, że po tych wszystkich latach nadal nie możemy w ogóle tworzyć prototypowych nagłówków i stopek w scenorysach i musimy polegać na tych NIB i ogólnie metodach rejestracji klas.
Ale wnioskowanie, że nie możesz użyć
register(_:forHeaderFooterViewReuseIdentifier:)
https://developer.apple.com/do ... ister, metoda API, która jest aktywnie używana od iOS 6. nie wyrzucajmy dziecka razem z wodą do kąpieli.
Cm.

poprzednia edycja
https://stackoverflow.com/revisions/36931047/3
Zobacz tę odpowiedź dla wersji Swift 2.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Odpowiedź Roba, choć brzmi przekonująco i przetrwała próbę czasu, jest błędna i zawsze była. Trudno jest stanąć samotnie wobec przytłaczającego tłumu akceptacji i wielu pozytywnych głosów, ale spróbuję zebrać się na odwagę, by powiedzieć prawdę.
Problem polega na tym, że nie można w magiczny sposób zamienić UIView na końcówce w UITableViewHeaderFooterView, po prostu deklarując go jako taki w inspektorze tożsamości. UITableViewHeaderFooterView ma ważne funkcje, które są kluczem do prawidłowego działania, oraz prosty UIView, bez względu na to, jak lubisz

rozgrywa się
, oni nie są.
  • UITableViewHeaderFooterView ma
    contentView
    https://developer.apple.com/do ... tviewa wszystkie niestandardowe widoki podrzędne powinny zostać dodane do tego, a nie do UITableViewHeaderFooterView. Ale UIView jest tajemniczo rzucany, ponieważ UITableViewHeaderFooterView nie ma tego elementu
    contentView
    we wskazówce. Więc kiedy Rob mówi „dodaj swoje kontrolki i ograniczenia do IB”, zmusza cię do dodania podtypów bezpośrednio do UITableViewHeaderFooterView , i nie w jego
    contentView
    . Dlatego nagłówek jest źle skonfigurowany.
  • Innym symptomem problemu jest to, że nie możesz nadać UITableViewHeaderFooterView koloru tła. Jeśli to zrobisz, w konsoli pojawi się następujący komunikat: Ustawianie koloru tła w UITableViewHeaderFooterView jest przestarzałe. Ustaw niestandardowy UIView z żądanym kolorem tła zamiast właściwości backgroundView. Ale w stalówce nie jesteś czy możesz nie ustawiaj koloru tła w swoim UITableViewHeaderFooterView i tobie naprawdę uzyskać tę wiadomość w konsoli.

Jaka jest więc prawidłowa odpowiedź na to pytanie? Nie

nie mozliwe

odpowiedź. Apple zrobiło tutaj wielką głupotę. Udostępnili metodę, która pozwala zarejestrować pióro jako źródło UITableViewHeaderFooterView, ale

w bibliotece obiektów nie ma UITableViewHeaderFooterView
... Dlatego ta metoda

nieprzydatny

.

Niemożliwy

Zaprojektuj UITableViewHeaderFooterView poprawnie we wskazówce.
To ogromny błąd w Xcode. Zgłosiłem błąd w tej sprawie

w 2013

rok, a on nadal tam siedzi otwarty. Rok po roku uzupełniam błąd, a Apple naciska, mówiąc: „Nie jest jeszcze ustalone, jak i kiedy ten problem zostanie rozwiązany”. Dlatego przyznają się do błędu, ale nic nie robią.
Jednak co ty

czy możesz

należy utworzyć normalny UIView na końcówce, a następnie w kodzie (w implementacji
viewForHeaderInSection
) załadować widok ręcznie z końcówki i umieścić go w
contentView
w nagłówku widok.
Na przykład przypuśćmy, że chcemy zaprojektować nasz nagłówek w końcówce i mamy w nagłówku etykietę, którą chcemy podłączyć do gniazda
lab
. Następnie potrzebujemy zarówno niestandardowej klasy nagłówka, jak i niestandardowej klasy widoku:
class MyHeaderView : UITableViewHeaderFooterView {
weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
@IBOutlet weak var lab : UILabel!
}

Rejestrujemy się

klasa

nasz widok nagłówka, a nie pióro:
self.tableView.register(MyHeaderView.self,
forHeaderFooterViewReuseIdentifier: self.headerID)

Jako plik

xib
, deklarujemy, że nasz widok to MyHeaderViewContent -

nie

MyHeaderView.
W
viewForHeaderInSection
wyciągamy widok z końcówki, wstawiamy go do nagłówka
contentView
i ustawiamy link do niego:
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
}

To straszne i denerwujące, ale to najlepsza rzecz, jaką możesz zrobić.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Nie mam wystarczającej reputacji, aby dodać komentarz do odpowiedzi Matta.
W każdym razie jedyną rzeczą, której tu brakuje, jest usunięcie wszystkich podglądów podrzędnych z UITableViewHeaderFooterView.contentView przed dodaniem nowych widoków. Spowoduje to zresetowanie ponownie użytej komórki do jej pierwotnego stanu i uniknięcie wycieków pamięci.
Anonimowy użytkownik

Anonimowy użytkownik

Potwierdzenie od:

Utwórz plik UITableViewHeaderFooterView i odpowiedni plik XIB.
class BeerListSectionHeader: UITableViewHeaderFooterView {
@IBOutlet weak var sectionLabel: UILabel!
@IBOutlet weak var abvLabel: UILabel!
}

Zarejestruj pióro w ten sam sposób, w jaki rejestrujesz komórkę widoku tabeli. Nazwa wskazówki i identyfikator ponownego wykorzystania muszą być zgodne z nazwami plików. (Xib nie ma identyfikatora ponownego wykorzystania).
func registerHeader {
let nib = UINib(nibName: "BeerListSectionHeader", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "BeerListSectionHeader")
}

Usuń z kolejki i użyj podobnego do komórki. Identyfikator to nazwa pliku.
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "BeerListSectionHeader") as! BeerListSectionHeader let sectionTitle = allStyles[section].name
header.sectionLabel.text = sectionTitle
header.dismissButton?.addTarget(self, action: #selector(dismissView), for: .touchUpInside)
return header
}

Nie zapomnij o wysokości nagłówka.
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return BeerListSectionHeader.height
}

Aby odpowiedzieć na pytania, Zaloguj się lub Zarejestruj się