Get Height of Table Contents in Swift - ios

I'm trying to get the height of all of a table's contents so I can update the container it is in. This will allow the container and other views in the scroll view to scroll together.

Swift:
var tableViewHeight: CGFloat {
tableView.layoutIfNeeded()
return tableView.contentSize.height
}
Objective-C
- (CGFloat)tableViewHeight {
[tableView layoutIfNeeded];
return [tableView contentSize].height;
}

Swift 3
override func viewDidLoad() {
super.viewDidLoad()
myTbleView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func viewWillDisappear(_ animated: Bool) {
myTbleView.removeObserver(self, forKeyPath: "contentSize")
super.viewWillDisappear(true)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "contentSize"){
if let newvalue = change?[.newKey]
{
let newsize = newvalue as! CGSize
tableViewHeightConstraint.constant = newsize.height
}
}
}
Hope this will help you.

var obs: NSKeyValueObservation?
obs = tableView.observe(\.contentSize, options: .new) { (_, change) in
guard let height = change.newValue?.height else { return }
self.constantHeight.constant = height
}

Related

addObserver with context to multiple collectionView

I have a controller that has multiple collectionView on it and I want to scroll as one. So I added an .addObserver to the collectionViews but I had issues with differentiating the two collectionViews so I added a context.
My question is if this is the correct way to differentiating the collectionViews for the "override func observeValue"?
Everything looks like its working fine but don't know if this is the correct way to do this and if I will have future issues?
private static var InicioRecetasCollectionViewContext = UnsafeMutableRawPointer.allocate(byteCount: 4 * 4, alignment: 1)
private static var InicioPedidoServerCollectionViewContext = UnsafeMutableRawPointer.allocate(byteCount: 4 * 4, alignment: 1)
//Set the heigh of the table view so it scrolls as one.
override func viewWillAppear(_ animated: Bool) {
self.InicioRecetasCollectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: InicioViewController.InicioRecetasCollectionViewContext)
self.InicioPedidoServerCollectionView.addObserver(self, forKeyPath: "contentSize", options: .new, context: InicioViewController.InicioPedidoServerCollectionViewContext)
}
override func viewWillDisappear(_ animated: Bool) {
self.InicioRecetasCollectionView.removeObserver(self, forKeyPath: "contentSize")
self.InicioPedidoServerCollectionView.removeObserver(self, forKeyPath: "contentSize")
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("contentSize")
if keyPath == "contentSize" {
if let newValue = change?[.newKey]{
let newSize = newValue as! CGSize
if(context == InicioViewController.InicioRecetasCollectionViewContext){
self.InicioProveedorAvisosCollectionViewHeightConstraint.constant = newSize.height
}
if(context == InicioViewController.InicioPedidoServerCollectionViewContext){
self.InicioPedidoServerCollectionViewHeightConstraint.constant = newSize.height
}
}
}
}

Observe layer frame size change in UICollectionViewCell

Is it possible to observe self.layer.frame.size change in UICollectionViewCell?
I tried to register observer do it like this:
override func awakeFromNib() {
super.awakeFromNib()
self.layer.addObserver(self, forKeyPath: "frame", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
}
And then observe like this:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print(keyPath ?? "NIL")
}
And it came out that observeValue method has been never called.
You can override the var frame and add your function on didSet
override var frame: CGRect {
didSet {
//do something
}
}

UITableViewCell Dynamic height with expandable UIView

As I want to create tableview cell that has dynamic height I don't know what height it will take after rendering the content.
And there is a UIView which is hidden. It will render when someone taps on the cell, the UIView is visible.
Now the problem arises when content is larger its starts hiding, and to make that view visible I need the height in Float.
// I am using this as a logic for cell expander.
class var expanderHeight : CGFloat { get { return 160 } }
class var defaultHeight :CGFloat { get { return 140 } }
func checkHeight(){
imageexpander.hidden = (frame.size.height < EaterysTableViewCell.expanderHeight)
}
func watchFrameChanges(){
if !isObserving {
addObserver(self, forKeyPath: "frame", options: [NSKeyValueObservingOptions.New, NSKeyValueObservingOptions.Initial], context: nil)
}
}
func ingnoreFrameChanges(){
if isObserving {
removeObserver(self, forKeyPath: "frame")
isObserving = false
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "frame" {
checkHeight()
}
}
I have three labels like this:
`|label one|
|label two|
|label three|`
How do I add constraints so that content of label one and label two do not get truncated when they have multiple lines (dynamically)?

iOS UITabBar selectionIndicatorImage Y position

I have this spec:
But, i have this:
I have this code:
self.mTabBar.selectionIndicatorImage = UIImage(named: "footer-blue-line")
How can I set the Y position for the selectionIndicatorImage?
Why aren't you using a single "selected" image that has a blue line below it ?
http://s18.postimg.org/c5bjg501h/line.png
Use the image given on the link on the place of "footer-blue-line".
Here you go. You just need to set class of Tab Bar to this class in the interface builder
class MyCustomTabBar: UITabBar
{
var didInit = false
override func layoutSubviews()
{
super.layoutSubviews()
if didInit == false
{
didInit = true
for subview in subviews {
// can't hookup to subviews, so do layer.sublayers
subview.layer.addObserver(self, forKeyPath: "sublayers", options: .New, context: nil)
}
}
}
deinit
{
for subview in subviews
{
subview.layer.removeObserver(self, forKeyPath: "sublayers")
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>)
{
// layer.delegate is usually the parent view
if let l = object as? CALayer, tbButton = l.delegate as? UIView where tbButton.window != nil
{
for v in tbButton.subviews
{
if String(v.dynamicType) == "UITabBarSelectionIndicatorView" {
v.setYOrigin(3)
}
}
}
}
}

How do I adjust my popover to the size of the content in my tableview in swift?

I'm using popoverPresentationController to show my popover. The UITableViewController 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)
}
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.
Checkout the preferredContentSize property of UIViewController:
let height = yourDataArray.count * Int(popOverViewController.tableView.rowHeight)
popOverViewController.preferredContentSize = CGSize(width: 300, height: height)
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#>)
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
}
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
}
}
}
First thing first: All comments are good and help full.
I have did little change in my logic which makes my VC as reusable component.
Calling this method inside viewWillAppear:(BOOL)animated:
-(void) setPopOverPreferedContentHeight {
if (self.popoverPresentationController && self.tableView.contentSize.height < MAX_POPOVER_HEIGHT) {
self.preferredContentSize=self.tableView.contentSize;
} else if (self.popoverPresentationController){
self.preferredContentSize = CGSizeMake(self.tableView.contentSize.width, MAX_POPOVER_HEIGHT);
}
}

Resources