I have a view controller with the below UI layout.
There is a header view at the top with 3 labels, a footer view with 2 buttons at the bottom and an uitableview inbetween header view and footer view. The uitableview is dynamically loaded and on average has about 6 tableview cells. One of the buttons in the footer view is take screenshot button where i need to take the screenshot of full tableview. In small devices like iPhone 6, the height of the table is obviously small as it occupies the space between header view and footer view. So only 4 cells are visible to the user and as the user scrolls others cells are loaded into view. If the user taps take screen shot button without scrolling the table view, the last 2 cells are not captured in the screenshot. The current implementation tried to negate this by changing table view frame to table view content size before capturing screenshot and resetting frame after taking screenshot, but this approach is not working starting iOS 13 as the table view content size returns incorrect values.
Current UI layout implementation
Our first solution is to embed the tableview inside the scrollview and have the tableview’s scroll disabled. By this way the tableview will be forced to render all cells at once. We used the below custom table view class to override intrinsicContentSize to make the tableview adjust itself to correct height based on it contents
class CMDynamicHeightAdjustedTableView: UITableView {
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
override var contentSize: CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
}
}
But we are little worried about how overriding intrinsicContentSize could affect performance and other apple’s internal implementations
So our second solution is to set a default initial height constraint for tableview and observe the tableview’s content size keypath and update the tableview height constraint accordingly. But the content size observer gets called atleast 12-14 times before the screen elements are visible to the user.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.confirmationTableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentSize" {
if object is UITableView {
if let newvalue = change?[.newKey], let newSize = newvalue as? CGSize {
self.confirmationTableViewHeightConstraint.constant = newSize.height
}
}
}
}
- Will the second approach impact performance too?
- What is the better approach of the two?
- Is there any alternate solution?
2
Answers
I am not sure, but if I understood correctly when you screenshot the TableView the last 2 cells are not loaded because of the tableview being between the Header and Footer. Here are two options I would consider:
Option 1
Try to make the TableView frame start from the Header and have the height of the Unscreen.main.bounds.height – the Header view frame. This would mean that the tableView will expand toward the end of the screen. Then add the Footer over the tableView in the desired relation.
Option 2
Try before screenshooting, to reloadRows at two level below the current Level. You can get the current indexPath of the UITableView, when the TableView reloads it from its delegate, store it somewhere always the last indexPath used, and when screenshot reload the two below.
You can "temporarily" change the height of your table view, force it to update, render it to a
UIImage
, and then set the height back.Assuming you have your "Header" view constrained to the top, your "Footer" view constrained to the bottom, and your table view constrained between them…
Add a class var/property for the table view’s bottom constraint:
then set that constraint:
When you want to "capture" the table:
Here is a complete example you can run to test it:
We give the table 12 rows, each with 1, 2, 3 or 4 lines of text so it will definitely be too many rows to see on the screen. Tapping on the "Capture Table" button will capture the table to a
UIImage
and then display that image. Tap on the image to dismiss it: