ios Swift - 如何使用 XIB 文件创建自定义 viewForHeaderInSection?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/36926612/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Swift - How creating custom viewForHeaderInSection, Using a XIB file?
提问by iamburak
I can create simple custom viewForHeaderInSection in programmatically like below. But I want to do much more complex things maybe connection with a different class and reach their properties like a tableView cell. Simply, I want to see what I do.
我可以像下面这样以编程方式创建简单的自定义 viewForHeaderInSection 。但我想做更复杂的事情,可能与不同的类连接并达到它们的属性,如 tableView 单元格。简单地说,我想看看我做了什么。
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
}
Is there anyone to explain how can I create a custom tableView header view using xib? I have encountered with old Obj-C topics but I'm new with Swift language. If someone explain as detailed, It would be great.
有没有人解释如何使用 xib 创建自定义 tableView 标题视图?我遇到过旧的 Obj-C 主题,但我是 Swift 语言的新手。如果有人详细解释,那就太好了。
1.issue:Button @IBAction doesn't connect with my ViewController. (Fixed)
1.issue:按钮@IBAction 未与我的 ViewController 连接。(固定的)
Solved with File's Owner, ViewController base class (clicked left outline menu.)
使用 File's Owner、ViewController 基类解决(单击左侧大纲菜单。)
2.issue:Header height problem (Fixed)
2.issue:标题高度问题(已修复)
Solved adding headerView.clipsToBounds = truein viewForHeaderInSection: method.
解决了在viewForHeaderInSection方法中添加headerView.clipsToBounds = true的问题。
For constraint warningsthis answer solved my problems:
对于约束警告,这个答案解决了我的问题:
When I added ImageView even same height constraint with this method in viewController, it flow over tableView rows look like picture.
当我在 viewController 中使用此方法添加 ImageView 甚至相同的高度约束时,它流过 tableView 行看起来像图片。
func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 120
}
If I use, automaticallyAdjustsScrollViewInsets in viewDidLoad, In this case image flows under navigationBar. -fixed-
如果我使用,在 viewDidLoad 中自动调整ScrollViewInsets,在这种情况下图像在导航栏下流动。-固定的-
self.automaticallyAdjustsScrollViewInsets = false
3.issue:If button under View (Fixed)
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)")
}
}
}
回答by Rob
The typical process for NIB based headers would be:
基于 NIB 的标头的典型流程是:
Create
UITableViewHeaderFooterView
subclass with, at the least, an outlet for your label. You might want to also give it some identifier by which you can reverse engineer to which section this header corresponds. Likewise, you may want to specify a protocol by which the header can inform the view controller of events (like the tapping of the button). Thus, in Swift 3 and later:// 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) } }
Create NIB. Personally, I give the NIB the same name as the base class to simplify management of my files in my project and avoid confusion. Anyway, the key steps include:
Create view NIB, or if you started with an empty NIB, add view to the NIB;
Set the base class of the view to be whatever your
UITableViewHeaderFooterView
subclass was (in my example,CustomHeader
);Add your controls and constraints in IB;
Hook up
@IBOutlet
references to outlets in your Swift code;Hook up the button to the
@IBAction
; andFor the root view in the NIB, make sure to set the background color to "default" or else you'll get annoying warnings about changing background colors.
In the
viewDidLoad
in the view controller, register the NIB. In Swift 3 and later:override func viewDidLoad() { super.viewDidLoad() tableView.register(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: CustomHeader.reuseIdentifier) }
In
viewForHeaderInSection
, dequeue a reusable view using the same identifier you specified in the prior step. Having done that, you can now use your outlet, you don't have to do anything with programmatically created constraints, etc. The only think you need to do (for the protocol for the button to work) is to specify its delegate. For example, 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 }
Obviously, if you're going to specify the view controller as the
delegate
for the button in the header view, you have to conform to that protocol:extension ViewController: CustomHeaderDelegate { func customHeader(_ customHeader: CustomHeader, didTapButtonInSection section: Int) { print("did tap button", section) } }
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
在您的银行代码网点引用;将按钮连接到
@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's 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) } }
This all sounds confusing when I list all the steps involved, but it's really quite simple once you've done it once or twice. I think it's simpler than building the header view programmatically.
当我列出所有涉及的步骤时,这一切听起来都令人困惑,但是一旦您完成了一两次,它就非常简单。我认为这比以编程方式构建标题视图更简单。
In matt's answer, he protests:
在马特的回答中,他抗议:
The problem, quite simply, is that you cannot magically turn a
UIView
in a nib into aUITableViewHeaderFooterView
merely by declaring it so in the Identity inspector.
问题很简单,你不能仅仅通过在身份检查器中声明它就神奇地将
UIView
笔尖中的 a 变成 aUITableViewHeaderFooterView
。
This is simply not correct. If you use the above NIB-based approach, the class that is instantiated for the root view of this header view isa UITableViewHeaderFooterView
subclass, not a UIView
. It instantiates whatever class you specify for the base class for the NIBs root view.
这是不正确的。如果您使用上述基于 NIB 的方法,则为此头视图的根视图实例化的类是UITableViewHeaderFooterView
子类,而不是UIView
. 它实例化您为 NIB 根视图的基类指定的任何类。
What is correct, though, is that some of the properties for this class (notably the contentView
) aren't used in this NIB based approach. It really should be optional property, just like textLabel
and detailTextLabel
are (or, better, they should add proper support for UITableViewHeaderFooterView
in IB). I agree that this is poor design on Apple's part, but it strikes me as a sloppy, idiosyncratic detail, but a minor issue given all the problems in table views. E.g., it is extraordinary that after all these years, that we still can't do prototype header/footer views in storyboards at all and have to rely on these NIB and class registration techniques at all.
但是,正确的是此类的某些属性(特别是contentView
)未在这种基于 NIB 的方法中使用。它真的应该是可选属性,就像textLabel
和detailTextLabel
是(或者,更好的是,它们应该UITableViewHeaderFooterView
在 IB 中添加适当的支持)。我同意这对 Apple 来说是糟糕的设计,但在我看来,这是一个草率、特殊的细节,但考虑到表视图中的所有问题,这只是一个小问题。例如,经过这么多年,我们仍然无法在故事板中进行原型页眉/页脚视图,而必须完全依赖这些 NIB 和类注册技术,这真是太了不起了。
But, it is incorrect to conclude that one cannot use register(_:forHeaderFooterViewReuseIdentifier:)
, an API method that has actively been in use since iOS 6. Let's not throw the baby out with the bath water.
但是,断定不能使用 是不正确的,这是register(_:forHeaderFooterViewReuseIdentifier:)
自 iOS 6 以来一直在使用的 API 方法。我们不要把婴儿和洗澡水一起倒掉。
See previous revisionof this answer for Swift 2 renditions.
请参阅此答案的先前修订版以了解 Swift 2 版本。
回答by matt
Rob's answer, though it sounds convincing and has withstood the test of time, is wrong and always was. It's difficult to stand alone against the overwhelming crowd "wisdom" of acceptance and numerous upvotes, but I'll try to summon the courage to tell the truth.
Rob 的回答虽然听起来很有说服力并且经受住了时间的考验,但它是错误的,而且一直都是错误的。很难独自面对压倒性的接受和无数赞成票的“智慧”,但我会努力鼓起勇气说实话。
The problem, quite simply, is that you cannot magically turn a UIView in a nib into a UITableViewHeaderFooterView merely by declaring it so in the Identity inspector. A UITableViewHeaderFooterView has important features that are key to its correct operation, and a plain UIView, no matter how you may castit, lacks them.
问题很简单,你不能仅仅通过在身份检查器中声明它来神奇地将笔尖中的 UIView 转换为 UITableViewHeaderFooterView。UITableViewHeaderFooterView 具有对其正确操作至关重要的重要功能,而一个普通的 UIView,无论您如何投射它,都缺少它们。
A UITableViewHeaderFooterView has a
contentView
, and all your custom subviews must be added to this, not to the UITableViewHeaderFooterView.But a UIView mysteriously cast as a UITableViewHeaderFooterView lacks this
contentView
in the nib. Thus, when Rob says "Add your controls and constraints in IB", he is having you add subviews directly to the UITableViewHeaderFooterView, and notto itscontentView
. The header thus ends up incorrectly configured.Another sign of the issue is that you are not permitted to give a UITableViewHeaderFooterView a background color. If you do, you'll get this message in the console:
Setting the background color on UITableViewHeaderFooterView has been deprecated. Please set a custom UIView with your desired background color to the backgroundView property instead.
But in the nib, you cannot helpsetting a background color on your UITableViewHeaderFooterView, and you doget that message in the console.
UITableViewHeaderFooterView 有一个
contentView
,并且必须将所有自定义子视图添加到此,而不是添加到 UITableViewHeaderFooterView。但是一个神秘地转换为 UITableViewHeaderFooterView 的 UIView
contentView
在笔尖中缺少这个。因此,当 Rob 说“在 IB 中添加您的控件和约束”时,他让您将子视图直接添加到 UITableViewHeaderFooterView,而不是它的contentView
. 因此,标头最终配置不正确。该问题的另一个迹象是您不允许为 UITableViewHeaderFooterView 提供背景颜色。如果这样做,您将在控制台中收到此消息:
不推荐在 UITableViewHeaderFooterView 上设置背景颜色。请改为将具有所需背景颜色的自定义 UIView 设置为 backgroundView 属性。
但是在笔尖中,您无法帮助在 UITableViewHeaderFooterView 上设置背景颜色,并且您确实在控制台中收到了该消息。
So what's the right answer to the question? There's no possibleanswer. Apple has made a huge goof here. They have provided a method that allows you to register a nib as the source of your UITableViewHeaderFooterView, but there is no UITableViewHeaderFooterView in the Object Library. Therefore this method is useless. It is impossibleto design a UITableViewHeaderFooterView correctly in a nib.
那么这个问题的正确答案是什么?有没有可能的答案。苹果在这方面犯了大错。他们提供了一种方法,允许您将笔尖注册为 UITableViewHeaderFooterView 的源,但是Object Library 中没有 UITableViewHeaderFooterView。因此这种方法是没有用的。在笔尖中正确设计 UITableViewHeaderFooterView是不可能的。
This is a huge bug in Xcode. I filed a bug report on this matter in 2013and it is still sitting there, open. I refile the bug year after year, and Apple keeps pushing back, saying "It has not been determined how or when the issue will be resolved." So they acknowledge the bug, but they do nothing about it.
这是 Xcode 中的一个巨大错误。我在 2013 年提交了关于这个问题的错误报告,它仍然坐在那里,打开。我年复一年地重新提交错误,Apple 一直在反击,说“尚未确定问题将如何或何时解决。” 所以他们承认这个错误,但他们什么都不做。
What you cando, however, is design a normal UIView in the nib, and then, in code (in your implementation of viewForHeaderInSection
), load the view manually from the nib and stuff it into the contentView
of your header view.
但是,您可以做的是在笔尖中设计一个普通的 UIView,然后在代码中(在您的 实现中viewForHeaderInSection
),从笔尖手动加载视图并将其填充到contentView
标题视图中。
For example, let's say we want to design our header in the nib, and we have a label in the header to which we want to connect an outlet lab
. Then we need both a custom header class and a custom view class:
例如,假设我们想在笔尖中设计我们的头,我们在头中有一个标签,我们想将一个 outlet 连接到它lab
。然后我们需要一个自定义头类和一个自定义视图类:
class MyHeaderView : UITableViewHeaderFooterView {
weak var content : MyHeaderViewContent!
}
class MyHeaderViewContent : UIView {
@IBOutlet weak var lab : UILabel!
}
We register our header view's class, not the nib:
我们注册头视图的类,而不是笔尖:
self.tableView.register(MyHeaderView.self,
forHeaderFooterViewReuseIdentifier: self.headerID)
In the view xibfile, we declare our view to be a MyHeaderViewContent — nota MyHeaderView.
在视图xib文件中,我们将视图声明为 MyHeaderViewContent,而不是MyHeaderView。
In viewForHeaderInSection
, we pluck the view out of the nib, stuff it into the contentView
of the header, and configure the reference to it:
在 中viewForHeaderInSection
,我们从笔尖中取出视图,将其塞入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
}
It's dreadful and annoying, but it is the best you can do.
这很可怕也很烦人,但这是你能做的最好的事情。
回答by copied
I don't have enough reputation to add comment to Matt answer.
我没有足够的声誉来为 Matt 答案添加评论。
Anyway, the only thing missing here is to remove all subviews from UITableViewHeaderFooterView.contentView before adding new views. This will reset reused cell to initial state and avoid memory leak.
无论如何,这里唯一缺少的是在添加新视图之前从 UITableViewHeaderFooterView.contentView 中删除所有子视图。这会将重用的单元重置为初始状态并避免内存泄漏。
回答by trishcode
Create a UITableViewHeaderFooterView and its corresponding xib file.
创建一个 UITableViewHeaderFooterView 及其对应的 xib 文件。
class BeerListSectionHeader: UITableViewHeaderFooterView {
@IBOutlet weak var sectionLabel: UILabel!
@IBOutlet weak var abvLabel: UILabel!
}
Register the nib similarly to how you register a table view cell. The nib name and reuse identifier should match your file names. (The xib doesn't have a reuse id.)
注册笔尖与注册表格视图单元格的方式类似。笔尖名称和重用标识符应与您的文件名匹配。(xib 没有重用 ID。)
func registerHeader {
let nib = UINib(nibName: "BeerListSectionHeader", bundle: nil)
tableView.register(nib, forHeaderFooterViewReuseIdentifier: "BeerListSectionHeader")
}
Dequeue and use similarly to a cell. The identifier is the file name.
出列和使用类似于单元格。标识符是文件名。
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
}
Don't forget the header height.
不要忘记标题高度。
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return BeerListSectionHeader.height
}