Подтвердить что ты не робот

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

Я могу создать простой пользовательский 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)")
        }

    }
}
4b9b3361

Ответ 1

Типичный процесс для заголовков на основе 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 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. Лично я даю NIB то же имя, что и базовый класс, чтобы упростить управление моими файлами в моем проекте и избежать путаницы. Во всяком случае, ключевые шаги включают в себя:

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

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

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

    • @IBOutlet ссылки @IBOutlet к розеткам в вашем коде Swift;

    • @IBAction кнопку к @IBAction; а также

    • Для корневого представления в NIB обязательно установите цвет фона на "по умолчанию", иначе вы получите раздражающие предупреждения об изменении цвета фона.

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

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
    }
    
  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 model
        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 customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) {
            print("did tap button", section)
        }
    }
    

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


В матовом ответе он протестует:

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

Это просто не правильно. Если вы используете вышеупомянутый основанный на NIB подход, класс, который создается для корневого представления этого представления заголовка, является подклассом UITableViewHeaderFooterView, а не UIView. Он создает экземпляр любого класса, который вы указываете для базового класса для корневого представления NIB.

Однако правильно то, что некоторые свойства этого класса (особенно contentView) не используются в этом подходе, основанном на NIB. Это действительно должно быть необязательным свойством, так же как textLabel и detailTextLabel (или, что лучше, они должны добавить надлежащую поддержку UITableViewHeaderFooterView в IB). Я согласен, что это плохой дизайн со стороны Apple, но он кажется мне неряшливой, своеобразной деталью, но незначительной проблемой, учитывая все проблемы в табличных представлениях. Например, поразительно, что после всех этих лет мы все еще не можем делать прототипные представления верхнего/нижнего колонтитула в раскадровках и вынуждены вообще полагаться на эти NIB и методы регистрации классов.

Но неверно делать вывод, что нельзя использовать register(_:forHeaderFooterViewReuseIdentifier:), метод API, который активно используется начиная с iOS 6. Не давайте ребенку выплеснуть воду из ванны.


Смотрите предыдущую редакцию этого ответа для Swift 2 исполнения.

Ответ 2

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

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

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

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

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

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

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

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

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

Однако вы можете создать обычный UIView в nib, а затем в коде (в вашей реализации viewForHeaderInSection) загрузите представление вручную из contentView и contentView его в 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, а не MyHeaderView.

В viewForHeaderInSection мы contentView из contentView его в 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
}

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

Ответ 3

У меня недостаточно репутации, чтобы добавить комментарий к Мэтту.

Во всяком случае, единственное, чего не хватает, это удалить все подпункты из UITableViewHeaderFooterView.contentView перед добавлением новых представлений. Это приведет к сбросу повторно используемой ячейки в исходное состояние и предотвратит утечку памяти.

Ответ 4

Ответы Робы кажутся правильными, но Мэтт предоставил подробные сведения о том, как Роб ответил неправильно. Хотя, с другой стороны, Apple использовал метод Robs в этом примере. Перейдите по этой ссылке.

https://developer.apple.com/library/archive/samplecode/TableViewUpdates/Introduction/Intro.html#//apple_ref/doc/uid/DTS40010139-Intro-DontLinkElementID_2