How do I adjust my popover to the size of the content in my tableview in swift?
Solution 1
Checkout the preferredContentSize property of UIViewController:
let height = yourDataArray.count * Int(popOverViewController.tableView.rowHeight)
popOverViewController.preferredContentSize = CGSize(width: 300, height: height)
Solution 2
In your UITableViewController's viewDidLoad()
you can add an observer:
self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
Then add this method:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
self.preferredContentSize = tableView.contentSize
}
Lastly, in viewDidDisappear()
, make sure you remove the observer:
tableView.removeObserver(self, forKeyPath: "contentSize")
This way the popover will automatically adjust size to fit the content, whenever it is loaded, or changed.
Solution 3
Override the preferredContentSize
property in your extension of the uitableviewcontroller
as following:
override var preferredContentSize: CGSize {
get {
let height = calculate the height here....
return CGSize(width: super.preferredContentSize.width, height: height)
}
set { super.preferredContentSize = newValue }
}
For calculating the height check out tableView.rectForSection(<#section: Int#>)
Solution 4
Simple dynamic answer for Swift 4.x and Swift 5.x involving no size-computation (modern version of Bo Frese answer):
private var contentSizeObserver : NSKeyValueObservation?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
contentSizeObserver = tableView.observe(\.contentSize) { [weak self] tableView, _ in
self?.preferredContentSize = CGSize(width: 320, height: tableView.contentSize.height) // Here I fixed the width but you can do whatever you want
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
contentSizeObserver?.invalidate()
contentSizeObserver = nil
}
Solution 5
For swift 4 if you want to observe the content size I found this to be the optimal solution. Reporting it here since I did not find a complete example online:
class MyTableViewController: UITableViewController {
private var kvoContext = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addObserver(self, forKeyPath: #keyPath(tableView.contentSize), options: .new, context: &kvoContext)
}
override func viewDidDisappear(_ animated: Bool) {
removeObserver(self, forKeyPath: #keyPath(tableView.contentSize))
super.viewDidDisappear(animated)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &kvoContext, keyPath == #keyPath(tableView.contentSize),
let contentSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
self.popoverPresentationController?.presentedViewController.preferredContentSize = contentSize
}
}
}
Henny Lee
Updated on January 12, 2020Comments
-
Henny Lee over 4 years
I'm using
popoverPresentationController
to show my popover. TheUITableViewController
used to show as popover is created programmatically and will usually contain 1 to 5 rows. How do I set up this popover to adjust the size to the content of the tableview?Code for my popover:
if recognizer.state == .Began { let translation = recognizer.locationInView(view) // Create popoverViewController var popoverViewController = UITableViewController() popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover popoverViewController.tableView.backgroundColor = UIColor.popupColor() // Settings for the popover let popover = popoverViewController.popoverPresentationController! popover.delegate = self popover.sourceView = self.view popover.sourceRect = CGRect(x: translation.x, y: translation.y, width: 0, height: 0) popover.backgroundColor = UIColor.popupColor() presentViewController(popoverViewController, animated: true, completion: nil) }
-
Henny Lee about 9 yearsthanks, quite simpel. I tried
preferredContentSize
before but didn't think of using the count of my array :o. When I use this solution, it only works after I've accessedpopoverViewController.tableView.rowHeight
first or else the count and height will just return0
. How can I do this without accessing the height first? -
zisoft about 9 yearsI usually set
tableview.rowHeight = 44
inviewDidLoad
. -
Jordan Wood over 7 yearsThis is getting called before rows get added to my table, so
rectForSection
has a height of zero. -
Aditya almost 7 yearsThanks for the answer Adrian and Bo. I want to disable popover's animation when it is resizing. How can I dot that?
-
Ashley Mills over 6 years@AdityaShinde Please ask this as another question
-
Trev14 about 5 yearsI feel uncomfortable overriding the
observeValue
method. Adding the super method crashes...shouldn't there be a better way to observe changes? -
Christian Di Lorenzo about 5 years@Trev14
observeValue
is actually a super common way of observing changes for a key path. You shouldn't be calling super at all. It's extremely rare to call super in that method. BTW, this answer is much better than the others since it doesn't require a manual calculation. -
dec-vt100 about 5 yearsah, much better than any other solutions. Too bad RXSwift doesn't have an observable for that. And I'm too lazy to write my own right now.
-
Zaphod over 4 yearsYou should add it in you
viewWillAppear
if you remove it in theviewDidDisappear
. Moreover, you also should use a not nil context. -
Greg Brown about 4 yearsThis assumes a fixed row height, which is not ideal.
-
Saranjith almost 4 yearsyou saved my life!!
-
Antoine Rucquoy over 3 years⚠️ ⚠️ ⚠️ UI Handling into KVO completion block should be run on Main Queue. I edited the sample above.
-
Zaphod over 3 yearsHello @AntoineRucquoy, in fact, you don't need to run it on the main queue, because it's already running onto it. In fact the KVO handler is run on the queue performing the modification. As the observed property, here
\.contentSize
, is updated only on the main thread (by callingupdateData
and friends) you are sure your handler will be executed on the main thread. -
Antoine Rucquoy over 3 yearsHello @Zaphod, I had crashes while displaying my ViewController (where I'm using KVO on the contentSize's UITableView) into a forced popover on a mobile device (not an iPad, strangely). Maybe this is purely depending on how is my project built. However, I still assume that this is always safer to perform UI changes on the main thread.
-
Zaphod over 3 years@AntoineRucquoy I agree, it's always safer. But it seems the problem you are facing is just a symptom and you just hide it when you fix it by forcing the call on the main thread. You should check why your content size is not updated from the main thread in the first place.
-
swiftyboi over 2 yearsAny way to do this and maintain size class? Setting this property on iPad is causing me to see my iPhone layout.