I wrote the source code at the bottom.
I gave File's Owner
the IconHeader
class and after connecting the imageView
as an IBOutlet
, when I run the application, my application crashes and I get the following error.
Thread 1: "[<NSObject 0x6000030c8860> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key iconImageView."
Then I removed ImageView
from inpector
and deleted IconHeader
in File's Owner
from class and added IconHeader
to View
's class. I chose Icon Header
from Object
tab while connecting ImageView
.
This time, when I run the application, the following text appears on the console. "Then I removed ImageView from inpector and deleted IconHeader in File's Owner from class and added IconHeader to View's class. I chose Icon Header from Object tab while connecting ImageView."
MainViewController:
final class MainViewController: UIViewController {
@IBOutlet private weak var tableView: UITableView!
var viewModel: MainViewModelProtocol? {
didSet {
viewModel?.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
preapreTableView()
registerCellsForTableView()
registerHeaderForTableView()
viewModel?.fetchAgents()
}
private func preapreTableView() {
tableView.delegate = self
tableView.dataSource = self
}
private func registerCellsForTableView() {
let descriptionCellName = String(describing: DescriptionCell.self)
let descriptionCellNib = UINib(nibName: descriptionCellName, bundle: .main)
tableView.register(descriptionCellNib, forCellReuseIdentifier: descriptionCellName)
}
private func registerHeaderForTableView() {
let iconHeaderName = String(describing: IconHeader.self)
let iconHeaderNib = UINib(nibName: iconHeaderName, bundle: nil)
tableView.register(iconHeaderNib, forHeaderFooterViewReuseIdentifier: iconHeaderName)
}
}
MainViewController Extension:
extension MainViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let section = viewModel?.cellType[section]
else { return nil }
if case .description = section {
guard let iconHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: IconHeader.self)) as? IconHeader
else { return nil }
return iconHeader
}
return nil
}
}
IconHeader:
final class IconHeader: UITableViewHeaderFooterView {
@IBOutlet weak var iconImageView: UIImageView!
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
fatalError("init(coder:) has not been implemented")
}
private func commonInit() {
if let view = loadViewFromNib() {
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.backgroundColor = .red
contentView.addSubview(view)
}
}
private func loadViewFromNib() -> UIView? {
let nibName = String(describing: IconHeader.self)
let nib = UINib(nibName: nibName, bundle: nil)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
First, you're doing more work than needed...
Let's start with changing the IconHeader
to this:
final class IconHeader: UITableViewHeaderFooterView {
@IBOutlet var iconImageView: UIImageView!
}
Next we create a new, Empty User Interface File:
and save it as IconHeader
(with default .xib extension).
Add a plain UIView
:
and set Simulated Metrics -> Size to Freeform:
Resize it to make it easy to work with (the size for the moment doesn't matter), and assign its class to IconHeader
:
Drag in a UIImageView
... constrain it centered, at 30x30 ... give it a systemYellow background so we can see its frame, and any image (I used the Swift SF Symbol with Red Tint color) ... and connect the @IBOutlet
:
Now let's use a simplified view controller so we can run it without your viewModel
. We'll use a default UITableViewCell
class, 10 sections, 2 rows in each section:
final class MainViewController: UIViewController {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
preapreTableView()
registerCellsForTableView()
registerHeaderForTableView()
}
private func preapreTableView() {
tableView.delegate = self
tableView.dataSource = self
}
private func registerCellsForTableView() {
// register a default cell
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
}
private func registerHeaderForTableView() {
let iconHeaderName = String(describing: IconHeader.self)
let iconHeaderNib = UINib(nibName: iconHeaderName, bundle: nil)
tableView.register(iconHeaderNib, forHeaderFooterViewReuseIdentifier: iconHeaderName)
}
}
extension MainViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 10
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
c.textLabel?.text = "\(indexPath)"
return c
}
}
extension MainViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let iconHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: IconHeader.self)) as? IconHeader
else { return nil }
return iconHeader
}
}
Running that, we get:
Unfortunately, even though we are not setting any background colors, you will see this error / warning message for each section header in the debug console:
[TableView] Changing the background color of UITableViewHeaderFooterView
is not supported. Use the background view configuration instead.
To get rid of that, in your xib, change the view's background color from System Background
to Default
:
No more error messages.
To actually set the background color, we can do that either in viewForHeaderInSection
:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let iconHeader = tableView.dequeueReusableHeaderFooterView(withIdentifier: String(describing: IconHeader.self)) as? IconHeader
else { return nil }
iconHeader.contentView.backgroundColor = .cyan
return iconHeader
}
Output:
or, we can set a default color in the IconHeader
cell class init:
final class IconHeader: UITableViewHeaderFooterView {
@IBOutlet var iconImageView: UIImageView!
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
contentView.backgroundColor = .green
}
}