Swift - Как создать пользовательский viewForHeaderInSection, используя XIB-файл?

21

Я могу создать простой пользовательский viewForHeaderInSection в программном виде, как показано ниже. Но я хочу сделать гораздо более сложные вещи, возможно, соединение с другим классом и достичь их свойств, таких как cellView. Просто я хочу посмотреть, что я делаю.

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
}

Можно ли объяснить, как я могу создать собственное представление заголовка таблицы TableView с помощью xib? Я столкнулся со старыми темами Obj-C, но я новичок в языке Swift. Если кто-то объяснит, как подробно, было бы здорово.

  

1.issue: Кнопка @IBAction не подключается к моему ViewController. <Сильный> (фиксированный)

Решено с владельцем файла, базовым классом ViewController (меню с левой кнопкой мыши).

  

2.issue: Проблема с высотой заголовка (исправлена)

Решено добавить headerView.clipsToBounds = true в viewForHeaderInSection : метод.

Для предупреждений ограничений этот ответ решил мои проблемы :

Когда я добавил ImageView даже такое же ограничение по высоте с помощью этого метода в viewController, он перетекает через таблицы tableView выглядят как изображение .

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

Если я использую, автоматическиAdjustsScrollViewInsets в viewDidLoad, в этом случае изображение переходит в navigationBar. -fixed -

self.automaticallyAdjustsScrollViewInsets = false
  

3.issue: Если кнопка находится под пунктом просмотра (исправлено)

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

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

    }
}
    
задан burakgunduz 29.04.2016 в 00:33
источник
  • Попробуйте найти здесь: stackoverflow.com/questions/9219234/... –  Curmudgeonlybumbly 29.04.2016 в 00:49
  • FWIW, принятый ответ на этот другой вопрос - это решение kludgy, которое использовалось до того, как iOS предложила возможности декомпрессии для представлений заголовка / нижнего колонтитула. –  Rob 29.04.2016 в 08:41

2 ответа

50

Типичный процесс для заголовков на основе NIB:

  1. Создайте подкласс класса UITableViewHeaderFooterView , по крайней мере, для вашей метки. Возможно, вы захотите также дать ему некоторый идентификатор, с помощью которого вы можете перепроектировать, в какой раздел соответствует этот заголовок. Аналогично, вы можете указать протокол, с помощью которого заголовок может информировать контроллер событий о событиях (например, нажатие кнопки). Таким образом, в Swift 3:

    // if you want your header to be able to inform view controller of key events, create protocol
    
    protocol CustomHeaderDelegate: class {
        func didTapButton(in section: Int)
    }
    
    // define CustomHeader class with necessary 'delegate', '@IBOutlet' and '@IBAction':
    
    class CustomHeader: UITableViewHeaderFooterView {
        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
    
        @IBAction func didTapButton(_ sender: AnyObject) {
            delegate?.didTapButton(in: sectionNumber)
        }
    
    }
    
  2. Создайте NIB. Лично я даю NIB то же имя, что и базовый класс, чтобы упростить управление моими файлами в моем проекте и избежать путаницы. В любом случае, ключевые шаги включают в себя:

    • Создайте представление NIB, или если вы начали с пустой NIB, добавьте представление в NIB;

    • Установите базовый класс представления как ваш подкласс UITableViewHeaderFooterView (в моем примере, CustomHeader );

    • Добавьте свои элементы управления и ограничения в IB;

    • Подключить @IBOutlet ссылок на выходы вашего кода Swift;

    • Подключите кнопку к @IBAction

  3. В viewDidLoad в контроллере представления зарегистрируйте NIB. В Swift 3:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "CustomHeader")
    }
    

    Или в Swift 2:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.registerNib(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "CustomHeader")
    }
    
  4. В viewForHeaderInSection отменить повторное использование с использованием того же идентификатора, который вы указали на предыдущем шаге. Сделав это, вы теперь можете использовать свою розетку, вам не нужно ничего делать с программно созданными ограничениями и т. Д. Единственное, что вам нужно сделать (для протокола работы кнопки) - указать его делегат. Например, в 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
    }
    

    Или, в Swift 2:

    override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headerView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("CustomHeader") as! CustomHeader
    
        headerView.customLabel.text = content[section].name
        headerView.sectionNumber = section
        headerView.delegate = self
    
        return headerView
    }
    
    override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 44  // or whatever
    }
    
  5. Очевидно, что если вы укажете контроллер представления как delegate для кнопки в представлении заголовка, вы должны соответствовать этому протоколу:

    extension ViewController: CustomHeaderDelegate {
        func didTapButton(in section: Int) {
            print("\(section)")
        }
    }
    

Все это сбивает с толку, когда я перечисляю все шаги, но это очень просто, как только вы это сделали один или два раза. Я думаю, что это проще, чем программирование заголовка.

    
ответ дан Rob 29.04.2016 в 08:29
источник
  • Установил ли владелец файла NIB ваш класс контроллера вида? Если это так, когда вы выбираете кнопку, контроллер просмотра должен быть одним из двух классов, доступных в параметре «Автоматически» в редакторе помощника. –  Rob 29.04.2016 в 15:00
  • Я бы предложил вам изучить вашу иерархию представлений (например, используя отладчик вида) и просто посмотреть, что такое надстройка кнопки. Вы, например, поместили кнопку в контейнерный вид CustomHeader? –  Rob 29.04.2016 в 16:19
  • Я думаю, что стоит упомянуть: add 'self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension' и 'self.tableView.estimatedSectionHeaderHeight = 70' to 'viewDidLoad'. Если ваши ограничения соприкасаются с каждым подзором со всех сторон, вы получаете заголовки Self-Sized –  Torsten B 05.08.2016 в 15:31
  • Спасибо за подробный ответ! ;) –  Tom Calmon 23.08.2016 в 21:34
  • Чтобы повторить то, что @Rob сказал в вышеприведенном комментарии, убедитесь, что при подключении IBOutlet к вашему xib вы выбираете имя настраиваемого класса, а не File Owner. –  Jacob Davis 04.02.2018 в 22:09
Показать остальные комментарии
2

Ответ Роба, хотя он звучит убедительно и выдержал испытание временем, ошибочен и всегда был. Трудно стоять в одиночестве против подавляющей толпы «мудрости» принятия и многочисленных упреков, но я постараюсь призвать мужество сказать правду.

Проблема в том, что вы не можете волшебным образом превратить UIView в наконечник в UITableViewHeaderFooterView, просто объявив об этом в инспекторе Identity. UITableViewHeaderFooterView имеет важные функции, которые являются ключом к правильной работе, и простой UIView, независимо от того, как вы можете лить , им не хватает.

  • У UITableViewHeaderFooterView есть contentView , и все ваши пользовательские объекты должны быть добавлены к это не для UITableViewHeaderFooterView.

    Но UIView, таинственно представленный как UITableViewHeaderFooterView, не имеет этого contentView в nib. Таким образом, когда Роб говорит: «Добавьте свои элементы управления и ограничения в IB», он добавляет subviews непосредственно в UITableViewHeaderFooterView и not к своему contentView . Таким образом, заголовок неправильно настроен.

  • Еще один признак проблемы заключается в том, что вам не разрешено давать UITableViewHeaderFooterView цвет фона. Если вы это сделаете, вы получите это сообщение в консоли:

      

    Установка цвета фона в UITableViewHeaderFooterView устарела. Пожалуйста, настройте пользовательский UIView с желаемым цветом фона вместо свойства backgroundView.

    Но в nib вы не можете help установить цвет фона в вашем UITableViewHeaderFooterView, а вы do получите это сообщение в консоли.

Итак, каков правильный ответ на вопрос? Нет ответа no . Apple сделала здесь огромный шум. Они предоставили метод, который позволяет зарегистрировать наконечник в качестве источника вашего UITableViewHeaderFooterView, но в библиотеке объектов нет UITableViewHeaderFooterView . Поэтому этот метод бесполезен . невозможно правильно спроектировать UITableViewHeaderFooterView в nib.

Это огромная ошибка в Xcode. Я подал отчет об ошибке в этом вопросе в 2013 году , и он все еще сидит там, открыт. Я год за годом исправляю ошибку, и Apple продолжает отталкивать назад, говоря: «Не было определено, как и когда проблема будет решена». Поэтому они признают ошибку, но ничего не делают.

Однако, что вы можете создать нормальный UIView в nib, а затем в коде (в вашей реализации viewForHeaderInSection ) загрузите представление вручную из nib и прочее это в contentView вашего заголовка.

Например, предположим, что мы хотим создать наш заголовок в nib, и у нас есть метка в заголовке, к которой мы хотим подключить выход lab . Тогда нам нужны как пользовательский класс заголовка, так и пользовательский класс вида:

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

Мы регистрируем класс класса заголовка , а не nib:

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

В представлении xib мы объявляем наше представление MyHeaderViewContent - not MyHeaderView.

В viewForHeaderInSection мы вырваем представление из nib, запишем его в contentView заголовка и настроим ссылку на него:

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
}

Это ужасно и раздражает, но это лучшее, что вы можете сделать.

    
ответ дан matt 29.04.2018 в 15:22
источник