Home » Swift » Swift – How creating custom viewForHeaderInSection, Using a XIB file?

Swift – How creating custom viewForHeaderInSection, Using a XIB file?

Posted by: admin November 30, 2017 Leave a comment

Questions:

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.

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.

1.issue: Button @IBAction doesn’t connect with my ViewController. (Fixed)

Solved with File’s Owner, ViewController base class (clicked left outline menu.)

2.issue: Header height problem (Fixed)

Solved adding headerView.clipsToBounds = true in viewForHeaderInSection: method.

For constraint warnings this 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.

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

If I use, automaticallyAdjustsScrollViewInsets in viewDidLoad, In this case image flows under navigationBar. -fixed-

self.automaticallyAdjustsScrollViewInsets = false

3.issue: If button under View (Fixed)

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

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

    }
}
Answers:

The typical process for NIB based headers would be:

  1. 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:

    // 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. 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

  3. In the viewDidLoad in the view controller, register the NIB. In Swift 3:

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

    Or in Swift 2:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.registerNib(UINib(nibName: "CustomHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "CustomHeader")
    }
    
  4. 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
    }
    

    Or, in 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. 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 didTapButton(in section: Int) {
            print("\(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.