Adding image on top of UITableView

As part of version 1.2 of Gun Vault I decided to include the functionality of having the primary image of a gun included on top of the Gun Details screen. I wanted the image to take 1/3 of the screen when the device is in portrait mode …

Portrait view

and full screen when in landscape mode.

Landscape view

I thought this would be a simple task until I ended up spending over 2 hrs trying to fine-tune it so I figured I’d write about it.

Set up

We’ll keep it very simple - 1 grouped-style table with 3 sections and 5 rows inside each section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import UIKit
class ViewController: UITableViewController {
convenience init() {
self.init(style: UITableViewStyle.grouped)
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Gun details"
}
// ***********************************************
// MARK: - UITableViewDataSource
// ***********************************************
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// since it is a static view we don't care about recycling at this time
let result: UITableViewCell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
result.textLabel?.text = "Text for row at: \(indexPath.row)"
result.detailTextLabel?.text = "Details for row at: \(indexPath.row)"
return result
}
}

Primary image

In order to add a primary image on top we need to override 2 methods: heightForHeaderInSection and viewForHeaderInSection. The first one controlls the height of placeholder we are going to use for the image and the second one provides the actual image to be displayed.

Note that we are not using the device orientation but the statusBar orientation instead. This is because while the device can be rotated - the interface could be locked for rotation in the same time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
let orientation = UIApplication.shared.statusBarOrientation
if orientation.isLandscape {
return view.frame.height
} else {
return view.frame.height / 3
}
}
return UITableViewAutomaticDimension
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
// We'll assume that there is only one section for now.
if section == 0 {
let imageView: UIImageView = UIImageView()
imageView.clipsToBounds = true
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "gun")!
return imageView
}
return nil
}

Rotation support

The last thing that is left to add it the rotation support. Note that there are 3 phases that allow hooks. For our purpose we’ll use the before rotation and during rotation to invalidate the table layout. My first try was actually adding tableView.reloadData() instead but it resulted in a flicker of the screen so beginUpdates() and endUpdates() ended up being the better choice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// print("will execute before rotation")
self.tableView.beginUpdates()
coordinator.animate(alongsideTransition: { (context: UIViewControllerTransitionCoordinatorContext) in
// print("will execute during rotation")
self.tableView.endUpdates()
}) { (context: UIViewControllerTransitionCoordinatorContext) in
// print("will execute after rotation")
}
}

That’s it. Now you have a nice table view with a smart primary image.

Download full code here

Share Comments