'How to detect when UIView size is changed in swift ios xcode?
I am trying some stuffs out with CATiledLayer inside UIScrollView.
Somehow, the size of UIView inside the UIScrollView gets changed to a large number. I need to find out exactly what is causing this resize.
Is there a way to detect when the size of UIView(either frame, bounds) or the contentSize of UIScrollView is resized?
I tried
override var frame: CGRect {
didSet {
println("frame changed");
}
}
inside UIView subclass,
but it is only called once when the app starts, although the size of UIView is resized afterwards.
Solution 1:[1]
viewWillLayoutSubviews() and viewDidLayoutSubviews() will be called whenever the bounds change. In the view controller.
Solution 2:[2]
There's an answer here:
https://stackoverflow.com/a/27590915/5160929
Just paste this outside of a method body:
override var bounds: CGRect {
didSet {
// Do stuff here
}
}
Solution 3:[3]
You can also use KVO:
You can set a KVO like this, where view is the view you want to observe frame changes for:
self.addObserver(view, forKeyPath: "center", options: NSKeyValueObservingOptions.New, context: nil)
And you can get the changes with this notification:
override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: NSDictionary!, context: CMutableVoidPointer) {
}
The observeValueForKeyPath will be called whenever the frame of the view you are observing changes.
Also remember to remove the observer when your view is about to be deallocated:
view.removeObserver(self, forKeyPath:"center")
Solution 4:[4]
You can create a custom class, and use a closure to get the updated rect comfortably. Especially handy when dealing with classes (like CAGradientLayer which want you to give them a CGRect):
GView.swift:
import Foundation
import UIKit
class GView: UIView {
var onFrameUpdated: ((_ bounds: CGRect) -> Void)?
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
self.onFrameUpdated?(self.bounds)
}
}
Example Usage:
let headerView = GView()
let gradientLayer = CAGradientLayer()
headerView.layer.insertSublayer(gradientLayer, at: 0)
gradientLayer.colors = [
UIColor.mainColorDark.cgColor,
UIColor.mainColor.cgColor,
]
gradientLayer.locations = [
0.0,
1.0,
]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
headerView.onFrameUpdated = { _ in // here you have access to `bounds` and `frame` with proper values
gradientLayer.frame = headerView.bounds
}
If you are not adding your views through code, you can set the Custom Class property in storyboard to GView.
Please note that the name GView was chosen as a company measure and probably choosing something like FrameObserverView would be better.
Solution 5:[5]
This is a simple and not-too-hacky solution: You remember the last size of your view, compare it to the new size in an overridden layoutSubviews method, and then do something when you determine that the size has changed.
/// Create this as a private property in your UIView subclass
private var lastSize: CGSize = .zero
open override func layoutSubviews() {
// First call super to let the system update the layout
super.layoutSubviews()
// Check if:
// 1. The view is part of the view hierarchy
// 2. Our lastSize var doesn't still have its initial value
// 3. The new size is different from the last size
if self.window != nil, lastSize != .zero, frame.size != lastSize {
// React to the size change
}
lastSize = frame.size
}
Note that you don't have to include the self.window != nil check, but I assume that in most cases you are only interested in being informed of size changes for views that are part of the view hierarchy.
Note also that you can remove the lastSize != .zero check if you want to be informed about the very first size change when the view is initially displayed. Often we are not interested in that event, but only in subsequent size changes due to device rotation or a trait collection change.
Enjoy!
Solution 6:[6]
The answers are correct, although for my case the constraints I setup in storyboard caused the UIView size to change without calling back any detecting functions.
Solution 7:[7]
For UIViews, as easy as:
override func layoutSubviews() {
super.layoutSubviews()
setupYourNewLayoutHereMate()
}
Solution 8:[8]
You can use the FrameObserver Pod.
It is not using KVO or Method Swizzling so won't be breaking your code if the underlying implementation of UIKit ever changes.
whateverUIViewSubclass.addFrameObserver { frame, bounds in // get updates when the size of view changes
print("frame", frame, "bounds", bounds)
}
You can call it on a UIView instance or any of its subclasses, like UILabel, UIButton, UIStackView, etc.
Solution 9:[9]
STEP 1:viewWillLayoutSubviews
Called to notify the view controller that its view is about to layout its subviews
When a view's bounds change, the view adjusts the position of its subviews. Your view controller can override this method to make changes before the view lays out its subviews. The default implementation of this method does nothing.
STEP 2:viewDidLayoutSubviews
Called to notify the view controller that its view has just laid out its subviews.
When the bounds change for a view controller's view, the view adjusts the positions of its subviews and then the system calls this method. However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout.
Your view controller can override this method to make changes after the view lays out its subviews. The default implementation of this method does nothing.
Above these methods are called whenever bounds of UIView is changed
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | simons |
| Solution 2 | Community |
| Solution 3 | Community |
| Solution 4 | Sean Goudarzi |
| Solution 5 | |
| Solution 6 | Joon. P |
| Solution 7 | oskarko |
| Solution 8 | Sean Goudarzi |
| Solution 9 | bearacuda13 |
