Swift - 如何使用XIB文件创建自定义viewForHeaderInSection?

时间:2016-04-28 22:33:09

标签: ios swift uitableview xib

我可以在编程上创建简单的自定义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
}

有没有人解释如何使用xib创建自定义tableView标题视图?我遇到过旧的Obj-C主题,但我是Swift语言的新手。如果有人解释详细,那就太好了。

  

1.issue:按钮@IBAction无法与我的ViewController连接。的(固定)

使用File的Owner,ViewController基类解决(点击左侧轮廓菜单。)

  

2.issue:标题高度问题(已修复)

解决了在 viewForHeaderInSection :方法中添加 headerView.clipsToBounds = true 的问题。

对于约束警告 this answer solved my problems

当我在viewController中使用此方法添加ImageView甚至相同的高度约束时,它会流过tableView行look like picture

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

如果我使用,则在viewDidLoad中自动调整ScrollViewInsets,在这种情况下,图像流在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)")
        }

    }
}

4 个答案:

答案 0 :(得分:76)

基于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中添加控件和约束;

    • 将您的Swift代码中的商店引用@IBOutlet引用;

    • 将按钮连接到@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's 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)
        }
    }
    
  6. 当我列出所涉及的所有步骤时,这一切听起来都很混乱,但是一旦你完成了一两次,它就会变得非常简单。我认为它比以编程方式构建标题视图更简单。

    matt's answer,他抗议:

      

    问题很简单,就是你不能仅仅通过在身份检查器中声明它,将一个笔尖中的UIView神奇地变成UITableViewHeaderFooterView

    这根本不正确。如果使用上述基于NIB的方法,则为此标头视图的根视图实例化的类是 UITableViewHeaderFooterView子类,而不是UIView。它实例化您为NIB根视图为基类指定的任何类。

    但是,正确的是,此类的某些属性(尤其是contentView)并未在此基于NIB的方法中使用。它应该是可选属性,就像textLabeldetailTextLabel一样(或者更好的是,它们应该在IB中为UITableViewHeaderFooterView添加适当的支持)。我同意这在Apple的设计上是糟糕的设计,但它让我觉得这是一个草率的,特殊的细节,但考虑到表格视图中的所有问题,这是一个小问题。例如,经过这么多年,我们仍然无法在故事板中进行原型页眉/页脚视图,并且必须完全依赖于这些NIB和类注册技术。

    但是,如果不能使用register(_:forHeaderFooterViewReuseIdentifier:),这是一种自iOS 6以来一直在使用的API方法,这是不正确的。让我们不要把婴儿扔出洗澡水。

    请参阅此答案的previous revision以获取Swift 2格式。

答案 1 :(得分:28)

罗布斯的回答虽然听起来令人信服并且经受住了时间的考验,却是错误的,而且总是如此。很难独自对抗势不可挡的人群#34;智慧"接受和众多的赞成,但我会试着鼓起勇气说实话。

问题很简单,就是你不能仅仅通过在身份检查器中声明它,将nib中的UIView变成UITableViewHeaderFooterView。 UITableViewHeaderFooterView具有对其正确操作至关重要的重要功能,并且无论您如何投射它都缺乏简单的UIView。

  • UITableViewHeaderFooterView有contentView,所有自定义子视图都必须添加到此,而不是UITableViewHeaderFooterView。

    但是作为UITableViewHeaderFooterView神秘地投射的UIView在笔尖中缺少这个contentView。因此,当Rob说"在IB"中添加您的控件和约束时,他会让您将子视图直接添加到UITableViewHeaderFooterView ,而添加到其{ {1}}。因此,标题最终配置错误。

  • 此问题的另一个迹象是您不允许为UITableViewHeaderFooterView提供背景颜色。如果您这样做,您将在控制台中收到此消息:

      

    不推荐在UITableViewHeaderFooterView上设置背景颜色。请将具有所需背景颜色的自定义UIView设置为backgroundView属性。

    但是在笔尖中,你不能帮助在UITableViewHeaderFooterView上设置背景颜色,而你在控制台中获取该消息。

那么问题的正确答案是什么?那里的没有可能的答案。苹果在这里做了很大的努力。他们提供了一种方法,允许您将nib注册为UITableViewHeaderFooterView的源,但对象库中没有UITableViewHeaderFooterView 。因此,此方法无用。在笔尖中正确设计UITableViewHeaderFooterView是不可能的

这是Xcode中的一个巨大错误。我在2013年提交了关于此事的错误报告,它仍然坐在那里,打开。我年复一年地改变了这个错误,并且苹果不断推回,并且说#34;尚未确定如何或何时解决问题。"所以他们承认这个错误,但他们什么都不做。

可以做什么,但是,在nib中设计一个普通的UIView,然后在代码中(在contentView的实现中),从笔尖手动加载视图并将其填入标题视图的viewForHeaderInSection

例如,我们假设我们要在笔尖中设计标题,并且我们在标题中有一个标签,我们要将其连接到插座contentView。然后我们需要一个自定义头类和一个自定义视图类:

lab

我们注册了标题视图,而不是nib:

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

在视图 xib 文件中,我们将视图声明为MyHeaderViewContent - 而不是一个MyHeaderView。

self.tableView.register(MyHeaderView.self, forHeaderFooterViewReuseIdentifier: self.headerID) 中,我们从nib中取出视图,将其填入标题的viewForHeaderInSection,然后配置对它的引用:

contentView

这是可怕而烦人的,但这是你能做的最好的事情。

答案 2 :(得分:4)

创建一个UITableViewHeaderFooterView及其对应的xib文件。

class BeerListSectionHeader: UITableViewHeaderFooterView {
    @IBOutlet weak var sectionLabel: UILabel!
    @IBOutlet weak var abvLabel: UILabel!
}

注册笔尖类似于注册表格视图单元格的方式。笔尖名称和重用标识符应与您的文件名匹配。 (xib没有重用ID。)

func registerHeader {
    let nib = UINib(nibName: "BeerListSectionHeader", bundle: nil)
    tableView.register(nib, forHeaderFooterViewReuseIdentifier: "BeerListSectionHeader")
}

出队并类似于使用单元格。标识符是文件名。

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
}

别忘了页眉的高度。

 override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
     return BeerListSectionHeader.height
 }

答案 3 :(得分:2)

我的信誉不足,无法向马特答案添加评论。

无论如何,这里唯一缺少的是在添加新视图之前从UITableViewHeaderFooterView.contentView中删除所有子视图。这样会将重用的单元格重置为初始状态,并避免内存泄漏。