Observe layer frame size change in UICollectionViewCell - ios

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
}
}

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
}
}
}
}

Get Height of Table Contents in Swift

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
}

How to properly add Key-Value Observer to my button?

I have a UITableViewCell file and inside of it I do:
var followers: FollowersModel? {
didSet {
self.followerButton.addObserver(self, forKeyPath: "followerButtonTapped", options: .New, context: &kvoContext)
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print(keyPath)
}
where
private var kvoContext = 0
So, I want: When I click on the button in those Cell it'll run function from the ViewController. But on click on my button does not print anything.
It's my first time with KVOs, am I doing something wrong?
Well, KVO does not work like that. What - addObserver:forKeyPath:options:context: does is register observer to receive KVO notifications when property changed. In your case I suppose "followerButtonTapped" is not a property. Registering for observation
To handle button tapped you need to add target like this (or in IB):
cell.followerButton.addTarget(self, action: #selector(ViewController.onFollowerButtonTap(_:)), forControlEvents: .TouchUpInside)
And add method to your view controller:
func onFollowerButtonTap(sender: UIButton) {
}
To get the model object for this button you can use this extension:
extension UITableView {
func indexPathForView(view: UIView) -> NSIndexPath? {
let hitPoint = view.convertPoint(CGPoint.zero, toView: self)
return indexPathForRowAtPoint(hitPoint)
}
}

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