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)")
}
}
}
Ответы
Ответ 1
Типичный процесс для заголовков на основе NIB будет:
-
Создайте подкласс 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)
}
}
-
Создать NIB. Лично я даю NIB то же имя, что и базовый класс, чтобы упростить управление моими файлами в моем проекте и избежать путаницы. Во всяком случае, ключевые шаги включают в себя:
-
Создайте представление NIB или, если вы начали с пустого NIB, добавьте представление в NIB;
-
Установите базовый класс представления таким, каким был ваш подкласс UITableViewHeaderFooterView
(в моем примере CustomHeader
);
-
Добавьте свои элементы управления и ограничения в IB;
-
@IBOutlet
ссылки @IBOutlet
к розеткам в вашем коде Swift;
-
@IBAction
кнопку к @IBAction
; а также
-
Для корневого представления в NIB обязательно установите цвет фона на "по умолчанию", иначе вы получите раздражающие предупреждения об изменении цвета фона.
-
В viewDidLoad
в контроллере представления зарегистрируйте NIB. В Swift 3 и позже:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier)
}
-
В 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
}
-
Очевидно, что если вы собираетесь указать контроллер представления в качестве 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