UIScrollViewKeyboardDismissModeInteractive changing tableview height with keyboard - ios

Within a UIViewController I've set
self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeInteractive.
This is great because the user can drag the keyboard down from the tableview.
However, the tableview maintains it's current height when dragging down the keyboard. This looks odd because it leaves empty space between the keyboard and the scrollview.
How can I persistently track the frame of the keyboard so I may resize the tableview as the user drags the keyboard? I've tried using UIKeyboardWillChangeFrameNotification but that seems to only get called after the user finishes dragging.

Your table view shouldn't change its height to accommodate the keyboard.
Instead, the keyboard should be presented overtop of the table view, and you should adjust the contentInset and scrollIndicatorInsets properties on the table view so that the lower table content is not obscured by the keyboard. You need to update the scroll insets whenever the keyboard is presented or dismissed.
You don't have to do anything special while the keyboard is dismissed interactively, because the table content should already scroll down as the keyboard moves out of view.

I'd rather this not be the accepted answer, but for those of you out there also having trouble with this here's what worked for me.
Create a custom subclass of UIView.
In the subclass's willMoveToSuperview: method, add a KVO observer to the superview's center on iOS 8 and frame on lesser versions of iOS (remember to remove the old observer, you may want to use an instance variable to track this).
In your view controller add a 0.0 height input accessory view to the view controller via inputAccessoryView class override. Use your custom UIView subclass for this.
Back in the subclass, in observeValueForKeyPath:..., capture the frame of the view's superview, this should be the frame of the UIKeyboard.
Use NSNotificationCenter or some other means to then pass this frame back to your view controller for processing.
It's a pretty painful process and not guaranteed to work in future versions of iOS. There are likely a ton of edge cases that will pop up later since I just built this, but it's a good start. If anyone has a better idea I'll happily mark your answer as correct.

This is what I come up to, insted of using notification I use a delegate:
protocol MyCustomViewDelegate {
func centerChanged(center: CGPoint)
}
class MyCustomView: UIView{
private var centerContext = 0
var delegate: MyCustomViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func willMoveToSuperview(newSuperview: UIView?) {
super.willMoveToSuperview(newSuperview)
guard let newSuperview = newSuperview else {
self.superview?.removeObserver(self, forKeyPath: "center")
return
}
let options = NSKeyValueObservingOptions([.New, .Old])
newSuperview.addObserver(self, forKeyPath: "center", options: options, context: &centerContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
//print("CHANGE",keyPath)
if context == &centerContext {
guard let center = superview?.center else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
return
}
delegate?.centerChanged(center)
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}

Related

Intercepting tap even on the top UIView when multiple UIView are overlapping on top of each other

I have a UIView that fill the entire screen, then I'm adding multiple small circle UIView within that container view, I want those small circle's UIView to be draggable using UIPanGestureRecognizer. But sometimes they happen to be on top of each other making the top UIView not clickable at all, it always select the bottom ones.
In the container UIView I implemented hitTest to be able to select only those child views.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for planet in self.myPlanets.values {
if let presentation = planet.layer.presentation(), presentation.frame.contains(point) {
return planet
}
}
return nil
}
How can I make the top view receive the click even instead of the bottom view?
I handled draggable views by creating a UIView subclass, adding a UIPanGestureRecognizer and updating based on its inputs.
Using this method, whichever view is on top will receive the touch and you don't have to override hitTest on the superview.
I also added a delegate to update constraints if the view is constrained to the superview. By setting the delegate the UIView or ViewController (whichever is delegate) can update the constraints for the views you want to move.
Here's a simple implementation:
// Delegate protocol for managing constraint updates if needed
protocol DraggableDelegate: class {
// tells the delegate to move
func moveByTranslation(_ change: CGPoint)
}
class DraggableView: UIView {
var dragDelegate: DraggableDelegate?
init() {
// frame is set later if needed by creator
super.init(frame: CGRect.zero)
configureGestureRecognizers()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// setup UIPanGestureRecognizer
internal func configureGestureRecognizers() {
let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
addGestureRecognizer(panGR)
}
#objc func didPan(_ panGR: UIPanGestureRecognizer) {
// get the translation
let translation = panGR.translation(in: self).applying(transform)
if let delegate = dragDelegate {
// tell delegate to move
delegate.moveByTranslation(translation)
} else {
// move self
self.center.x += translation.x
self.center.y += translation.y
}
// reset translation
panGR.setTranslation(CGPoint.zero, in: self)
}
}
Here's how I implemented the delegate callback in the view controller using this view, since my view used constraints:
/// Moves the tool tray when dragging is triggered via the pan gesture recognizer in the tool tray (an instance of DraggableView).
///
/// - Parameter change: The x/y change provided by the pan gesture recognizer.
func moveByTranslation(_ change: CGPoint) {
// only interested in the y axis movements for this example
// update the constraint that moves this view
let newPos = constraint_tooltray_yAxis.constant + change.y
// this function limited the movement of the view to keep it within bounds
updateToolTrayPosition(newPos)
}

UIPanGesture inside UIScrollView in Swift

The problem in short, related to working with pan gesture inside a scrollView.
I have a canvas(which is an UIView itself but bigger in size) where i am drawing some UIView objects with pan gesture enabled over each of them(Each little UIView Objects I am talking about, are making using another UIView class).
Now the canvas can be bigger in height and width...which can be changed as per the user input.
So to achieve that I have placed the canvas inside a UIScrollView. Now the canvas is increasing or decreasing smoothly.
Those tiny UIView objects on the canvas can be rotated also.
Now the problem.
If I am not changing the canvas size(static) i.e. if its not inside the scrollview then each UIView objects inside the canvas are moving superbly and everything is working just fine with the following code.
If the canvas is inside the UIScrollView then the canvas can be scrollable right? Now inside the scrollview if I am panning the UIView objects on the canvas then those little UIView objects are not following the touch of the finger rather than its moving on another point when touch is moving on the canvas.
N.B. - Somehow I figured out that I need to disable the scrolling of the scrollview when any of the subviews are getting touch. For that thing I have implemented NSNotificationCenter to pass the signal to the parent viewController.
Here is the code.
This functions are defined inside the parent viewController class
func canvusScrollDisable(){
print("Scrolling Off")
self.scrollViewForCanvus.scrollEnabled = false
}
func canvusScrollEnable(){
print("Scrolling On")
self.scrollViewForCanvus.scrollEnabled = true
}
override func viewDidLoad() {
super.viewDidLoad()
notificationUpdate.addObserver(self, selector: "canvusScrollEnable", name: "EnableScroll", object: nil)
notificationUpdate.addObserver(self, selector: "canvusScrollDisable", name: "DisableScroll", object: nil)
}
This is the Subview class of the canvas
import UIKit
class ViewClassForUIView: UIView {
let notification: NSNotificationCenter = NSNotificationCenter.defaultCenter()
var lastLocation: CGPoint = CGPointMake(0, 0)
var lastOrigin = CGPoint()
var myFrame = CGRect()
var location = CGPoint(x: 0, y: 0)
var degreeOfThisView = CGFloat()
override init(frame: CGRect){
super.init(frame: frame)
let panRecognizer = UIPanGestureRecognizer(target: self, action: "detectPan:")
self.backgroundColor = addTableUpperViewBtnColor
self.multipleTouchEnabled = false
self.exclusiveTouch = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func detectPan(recognizer: UIPanGestureRecognizer){
let translation = recognizer.translationInView(self.superview!)
self.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y)
switch(recognizer.state){
case .Began:
break
case .Changed:
break
case .Ended:
notification.postNotificationName("EnableScroll", object: nil)
default: break
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
notification.postNotificationName("DisableScroll", object: nil)
self.superview?.bringSubviewToFront(self)
lastLocation = self.center
lastOrigin = self.frame.origin
let radians:Double = atan2( Double(self.transform.b), Double(self.transform.a))
self.degreeOfThisView = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI) )
if self.degreeOfThisView != 0.0{
self.transform = CGAffineTransformIdentity
self.lastOrigin = self.frame.origin
self.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_4))
}
myFrame = self.frame
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
notification.postNotificationName("EnableScroll", object: nil)
}
}
Now the scrollView is disabling its scroll perfectly whenever one of the UIView object is receiving touch over the canvas which is inside the scrollview but sometimes those UIView objects are not properly following the touch location over the canvas/screen.
I am using Swift 2.1 with Xcode 7 but anyone can tell me the missing things of mine or the solution using Objective-c/Swift?
Where do you set the lastLocation? I think it would be better for you to use locationInView and compute the translation by yourself. Then save the lastLocation on every event that triggers the method.
Also you might want to handle the Cancel state as well to turn the scrolling back on.
All of this does seem a bit messy though. The notifications are maybe not the best idea in your case nor is putting the gesture recognizers on the subviews. I think you should have a view which handles all those small views; it should also have a gesture recognizer that can simultaneously interact with other recognizers. When the gesture is recognized it should check if any of the subviews are hit and decide if any of them should be moved. If it should be moved then use the delegate to report that the scrolling must be disabled. If not then cancel the recognizer (disable+enable does that). Also in most cases where you put something movable on the scrollview you usually want a long press gesture recognizer and not a pan gesture recognizer. Simply use that one and set some very small minimum press duration. Note that this gesture works exactly the same as the pan gesture but can have a small delay to be detected. It is very useful in these kind of situations.
Update (The architecture):
The hierarchy should be:
View controller -> Scrollview -> Canvas view -> Small views
The canvas view should contain the gesture recognizer that controls the small views. When the gesture begins you should check if any of the views are hit by its location by simply iterating through the subviews and check if their frame contains a point. If so it should start moving the hit small view and it should notify its delegate that it has began moving it. If not it should cancel the gesture recognizer.
As the canvas view has a custom delegate it is the view controller that should implement its protocol and assign itself to the canvas view as a delegate. When the canvas view reports that the view dragging has begin it should disable the scrollview scrolling. When the canvas view reports it has stopped moving the views it should reenable the scrolling of the scroll view.
Create this type of view hierarchy
Create a custom protocol of the canvas view which includes "did begin dragging" and "did end dragging"
When the view controller becomes active assign self as a delegate to the canvas view. Implement the 2 methods to enable or disable the scrolling of the scroll view.
The canvas view should add a gesture recognizer to itself and should contain an array of all the small movable subviews. The recognizer should be able to interact with other recognizers simultaneously which is done through its delegate.
The Canvas gesture recognizer target should on begin check if any of the small views are hit and save it as a property, it should also save the current position of the gesture. When the gesture changes it should move the grabbed view depending on the last and current gesture location and re-save the current location to the property. When the gesture ends it should clear the currently dragged view. On begin and end it should call the delegate to notify the change of the state.
Disable or enable the scrolling in the view controller depending on the canvas view reporting to delegate.
I think this should be all.

How to detect when UIView size is changed in swift ios xcode? [duplicate]

This question already has answers here:
Is there a UIView resize event?
(7 answers)
Closed 7 months ago.
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.
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
}
}
viewWillLayoutSubviews() and viewDidLayoutSubviews() will be called whenever the bounds change. In the view controller.
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")
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.
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!
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.
For UIViews, as easy as:
override func layoutSubviews() {
super.layoutSubviews()
setupYourNewLayoutHereMate()
}
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.
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

Auto Layout constraint on CALayer IOS

Hi I am developing iPhone application in which I tried to set one side border for edittext. I did this in following way:
int borderWidth = 1;
CALayer *leftBorder = [CALayer layer];
leftBorder.borderColor = [UIColor whiteColor].CGColor;
leftBorder.borderWidth = borderWidth;
leftBorder.frame = CGRectMake(0, textField.frame.size.height - borderWidth, textField
.frame.size.width, borderWidth);
[textField.layer addSublayer:leftBorder];
I put some constraints on my edittext in IB so that when I rotate my device it will adjust width of text field according to that. My problem is that adjusts the width of edittext not adjusting the width of CALayer which I set for my edit text. So I think I have to put some constraints for my CALayer item as well. But I dont know how to do that. ANy one knows about this? Need Help. Thank you.
the whole autoresizing business is view-specific. layers don't autoresize.
what you have to do -- in code -- is to resize the layer yourself
e.g.
in a viewController you would do
- (void) viewDidLayoutSubviews {
[super viewDidLayoutSubviews]; //if you want superclass's behaviour...
// resize your layers based on the view's new frame
self.editViewBorderLayer.frame = self.editView.bounds;
}
or in a custom UIView you could use
- (void)layoutSubviews {
[super layoutSubviews]; //if you want superclass's behaviour... (and lay outing of children)
// resize your layers based on the view's new frame
layer.frame = self.bounds;
}
In Swift 5, you can try the following solution:
Use KVO, for the path "YourView.bounds" as given below.
self.addObserver(self, forKeyPath: "YourView.bounds", options: .new, context: nil)
Then handle it as given below.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "YourView.bounds" {
YourLayer.frame = YourView.bounds
return
}
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
More info about this here
According to this answer, layoutSubviews does not get called in all needed cases. I have found this delegate method more effective:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self != nil) {
[self.layer addSublayer:self.mySublayer];
}
return self;
}
- (void)layoutSublayersOfLayer:(CALayer*)layer
{
self.mySublayer.frame = self.bounds;
}
I implemented the method layoutSubviews of my custom view; inside this method I just update each sublayer's frame to match the current boundaries of my subview's layer.
-(void)layoutSubviews{
sublayer1.frame = self.layer.bounds;
}
My solution when I create with dashed border
class DashedBorderView: UIView {
let dashedBorder = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//custom initialization
dashedBorder.strokeColor = UIColor.red.cgColor
dashedBorder.lineDashPattern = [2, 2]
dashedBorder.frame = self.bounds
dashedBorder.fillColor = nil
dashedBorder.cornerRadius = 2
dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
self.layer.addSublayer(dashedBorder)
}
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
dashedBorder.frame = self.bounds
}
}
Swift 3.x KVO Solution
(Updated #arango_86's answer)
Add Observer
self.addObserver(
self,
forKeyPath: "<YOUR_VIEW>.bounds",
options: .new,
context: nil
)
Observe Values
override open func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
if (keyPath == "<YOUR_VIEW.bounds") {
updateDashedShapeLayerFrame()
return
}
super.observeValue(
forKeyPath: keyPath,
of: object,
change: change,
context: context
)
}
When I have to apply KVO I prefer to do it with RxSwift (Only if I am using it for more stuff, do not add this library just for this.)
You can apply KVO with this library too, or in the viewDidLayoutSubviews but I've had better results with this.
view.rx.observe(CGRect.self, #keyPath(UIView.bounds))
.subscribe(onNext: { [weak self] in
guard let bounds = $0 else { return }
self?.YourLayer.frame = bounds
})
.disposed(by: disposeBag)
Riffing off arango_86's answer – if you are applying the KVO fix within your own UIView subclass, the more "Swifty" way to do this is to override bounds and use didSet on it, like so:
override var bounds: CGRect {
didSet {
layer.frame = bounds
}
}
I had a similar problem - needing to set the frame of a 'CALayer' when using auto layout with views (in code, not IB).
In my case, I had a slightly convoluted hierarchy, having a view controller within a view controller. I ended up at this SO question and looked into the approach of using viewDidLayoutSubviews. That didn't work. Just in case your situation is similar to my own, here's what I found...
Overview
I wanted to set the frame for the CAGradientLayer of a UIView that I was positioning as a subview within a UIViewController using auto layout constraints.
Call the subview gradientView and the view controller child_viewController.
child_viewController was a view controller I'd set up as a kind of re-usable component. So, the view of child_viewController was being composed into a parent view controller - call that parent_viewController.
When viewDidLayoutSubviews of child_viewController was called, the frame of gradientView was not yet set.
(At this point, I'd recommend sprinkling some NSLog statements around to get a feel for the sequence of creation of views in your hierarchy, etc.)
So I moved on to try using viewDidAppear. However, due to the nested nature of child_viewController I found viewDidAppear was not being called.
(See this SO question: viewWillAppear, viewDidAppear not being called, not firing).
My current solution
I've added viewDidAppear in parent_viewController and from there I'm calling viewDidAppear in child_viewController.
For the initial load I need viewDidAppear as it's not until this is called in child_viewController that all of the subviews have their frames set. I can then set the frame for the CAGradientLayer...
I've said that this is my current solution because I'm not particularly happy with it.
After initially setting the frame of the CAGradientLayer - that frame can become invalid if the layout changes - e.g. rotating the device.
To handle this I'm using viewDidLayoutSubviews in child_viewController - to keep the frame of the CAGradientLayer within gradientView, correct.
It works but doesn't feel good.
(Is there a better way?)

UICollectionView not calling intrinsicContentSize

I have a UICollectionViewController which generates cells with a random color for testing purposes. Now that the UICollectionViewController is embedded in a UIScrollView, I want the scrollView to be the same size as it's contentSize.
I made a subclass of UICollectionView, implemented intrinsicContentSize method, and set the UICollectionView's class to my custom class in IB. However intrinsicContentSize never gets called. I have the exact same setup with an UITableView and there it works flawlessly.
Any ideas on this?
- (CGSize)intrinsicContentSize {
[self layoutIfNeeded];
return CGSizeMake(UIViewNoIntrinsicMetric, self.contentSize.height);
}
The correct answer is to do something like this
- (CGSize)intrinsicContentSize
{
return self.collectionViewLayout.collectionViewContentSize;
}
And call -invalidateContentSize whenever you think it needs to change (after reloadData for example).
In Interface Builder you may need to set placeholder intrinsic size constraints to avoid errors.
This subclassing and overriding of -intrinsicContentSize is useful if you want to grow a collection view's frame until it is constrained by a sibling or parent view
I'm not sure why it's happening. Here's another solution to this problem. Set up a height constraint on UICollectionView object. Then set its constant to be equal to self.collectionView.contentSize.height. I use a similar approach in my app, though I've got UITextView instead of UICollectionView.
UPDATE: I've found a way to do this with intrinsicContentSize: UITextView in UIScrollView using auto layout
Use this class to make UICollectionView update its intrinsic content size every time the content is changed.
class AutosizedCollectionView: UICollectionView {
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
registerObserver()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
registerObserver()
}
deinit {
unregisterObserver()
}
override var intrinsicContentSize: CGSize {
return contentSize
}
private func registerObserver() {
addObserver(self, forKeyPath: #keyPath(UICollectionView.contentSize), options: [], context: nil)
}
private func unregisterObserver() {
removeObserver(self, forKeyPath: #keyPath(UICollectionView.contentSize))
}
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?)
{
if keyPath == #keyPath(UICollectionView.contentSize) {
invalidateIntrinsicContentSize()
}
}
}
Though, you should understand that if you embed it into another scroll view, cell recycling will not work. The most appreciated way to deal with nested collection view is described here.

Resources