UICollectionViewCell sizing issue when rotating the iphone on simulator - ios

Currently I am working on a task where I am suppose expand UICollectionView cells based on the label inside. I am written constraints programmatically for this. The resizing occurs during this call.
override func preferredLayoutAttributedFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.width = ceil(size.width)
layoutAttributes.frame = frame
return layoutAttributes
}
This code works fine as expected, however, when I try to rotate, the cells which didn't appeared before rotation, the cells doesn't expand for them.
Is there any call I can make at cellForRowAtIndex or else place to force this preferredLayout to be called every time?
Or is there any easier process than this to avoid this calculation and directly update the cell size? I have updated estimateSize in layout, but I still need to call this method.

use viewWillTransition method
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.collectionView.collectionViewLayout.invalidateLayout()
}

Related

Collection View cells with full-width and dynamic height

I'm trying to create a feed of posts, like you'd see in the Twitter or Facebook app. From my understanding, I should be using a Collection View, so I've set one up.
But now, I'm confused as to how to make the cells full-width and the height of the cells dynamic, since the text within the cell can vary from just 1 line to many dozens of lines.
How would I go about doing this?
You may need UICollectionViewFlowLayout instance
let frame: CGRect = ...
let layout = UICollectionViewFlowLayout()
layout.estimatedItemSize = CGSize.zero
self.collectionView = UICollectionView(frame: someFrame, collectionViewLayout: layout)
About estimatedItemSize:
The default value of this property is CGSizeZero. Setting it to any other value causes the collection view to query each cell for its actual size using the cell’s preferredLayoutAttributesFitting(_:) method. If all of your cells are the same height, use the itemSize property, instead of this property, to specify the cell size instead.
Then you may design your UICollectionViewCell and in awakeFromNib() add an internal constraint to set exact width.
Then in cell for item at index path method you may do next:
let cell = ... as? MyCustomCell
cell?.load(model: someModel, widthConstraint: collectionView.bounds.width)
Important thing is to handle view size changes on rotation and split screen modes:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.collectionView.collectionViewLayout.invalidateLayout()
}) { _ in }
}
First that you would need is a UITextView.
Make sure it's scroll is disabled and you set the text content accordingly. For this open the Interface Builder, select the text view and there will be a section called "Scroll View". Uncheck Scrolling Enabled. This is very important so that UITextView skips the scrolling and stretches the content as much as required.
ViewController:
Secondly, in your ViewController you need to handle SubView Layout changes. This is called when rotation's are made or any size of the ViewController's view changes.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let layout = self.collection.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize(width: self.collection.frame.size.width-layout.sectionInset.left-layout.sectionInset.right-self.collection.contentInset.left - self.collection.contentInset.right, height: 100.0)
}
}
UICollectionViewCell Subclass:
Once you set the esitmatedItemSize, it's time to provider preferredLayoutAttributesFitting. You will have to override the UICollectionViewCell subclass.
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.size = self.getCellHeight()
return layoutAttributes
}
func getCellHeight()->CGSize{
self.setNeedsDisplay()
self.layoutIfNeeded()
let size = self.contentView.systemLayoutSizeFitting(UILayoutFittingExpandedSize)
return size
}

How to set Notification if view size changed when using multitasking | Swift

When testing my app on an iPad using multitasking and I change the app size on the app, the collection view cells need to be resized or else they don't fit in the view window.
I have took some measures such as
detecting device to change the size of the cells
measure view size to make sure cells fit within the window
Add an observer if user changes device orientation
This is all great but however when actually changing the window size when using multitasking, or going back to the full screen app, the cell sizes don't change and look out of place. This is until forced by switching orientation or switching view controllers so viewWillLoad or viewDidLoad gets executed again.
So how can I set a notification if the view.frame size changes ?
I think the right function to override is viewWillTransition(to:, with:). It is called whenever the container size is about to change.
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
self.collectionView?.collectionViewLayout.invalidateLayout()
}
I think you are over complicating the issue, just use the following so when the device is rotated or resized the collection view also changes accordingly.
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard let collectionLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else {
return
}
if UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeLeft ||
UIApplication.shared.statusBarOrientation == UIInterfaceOrientation.landscapeRight {
//here you can do the logic for the cell size if phone is in landscape
} else {
//logic if not landscape
}
collectionLayout.invalidateLayout()
}
I tried RileyDev answer but the app entered in an infinite loop.
There is no delegate that is executed just when the App's window bounds change, so I've made my own version of the viewWillTransition(to size and traitCollectionDidChange.
With the code below you can detect the window bounds resizing change even if the traits don't change, and resize your collection view cells accordingly invalidating the collection view layout.
var previousWindowBounds: CGRect?
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let currentBounds = view.window?.bounds,
let previousWindowBounds = previousWindowBounds, currentBounds != previousWindowBounds {
collectionView?.collectionViewLayout.invalidateLayout()
}
previousWindowBounds = view.window?.bounds
}

Property observer working for bounds but not frame

I'm working on this project and I have one scene on the Storyboard.
The scene is a TableViewController.
The table view have a custom prototype cell (linked to CustomCell.swift).
Inside the prototype cell there's a label and a custom UIView (linked to CustomView.swift). These elements have layout constraints relative to the contentView of the prototype cell.
Now, I want the stuff being drawn on my custom view to change when the size of the view changes, so that when the device rotates it is adjusted to that new cell width. Because of the constraints, the frame of CustomView will change when the CustomCell changes size, after the device is rotated. In order to detect this, I added two property observers to CustomView.swift:
override var frame: CGRect {
didSet {
print("Frame was set!")
updateDrawing()
}
}
override var bounds: CGRect {
didSet {
print("Bounds were set!")
updateDrawing()
}
}
When running the project, the second observer works fine when I rotate the device. The first observer does not. My question is why does the first observer not detect that the frame has changed?
.frame is computed from the .bounds and the .center of the view (and the transform), so it does not change. In order to react to rotation override this (starting from iOS8):
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
coordinator.animateAlongsideTransition({ (coordinator) -> Void in
// do your stuff here
// here the frame has the new size
}, completion: nil)
}

Resize UICollectionView after view transition

I've built detail view in Interface Builder showing informations and photos about some object. Because lenght of informations and number of photos will vary, all is nested in UIScrollView.
Photos are shown in UICollectionView, but I need to always show all contained photos, so I disabled scrolling and dynamically changing Height constraint of UICollectionView by this function (called when finishing rendering cells):
func resizePhotosCollectionView() {
photosCollectionViewHeightConstraint.constant = photosCollectionView.collectionViewLayout.collectionViewContentSize()
}
It works great until this UICollectionView needs resize (typically by device orientation change). I am trying to use UIViewControllerTransitionCoordinator in function:
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
but result is jerky because Height constraint changed after transition is complete.
Is there any way how to automatically resize UICollectionView after view transition? If not, how to animate Height constraint change during transition?
Using Xcode 6.1 for target IOS 8.
If I recall correctly, you need to layout the view immediately.
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil) { context in
self.resizePhotosCollectionView()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
}
Have you tried wrapping your constraint update into a view animation?
photosCollectionViewHeightConstraint.constant =
photosCollectionView.collectionViewLayout.collectionViewContentSize()
photosCollectionView.setNeedsUpdateConstraints()
UIView.animateWithDuration(0.3, animations: { () -> Void in
photosCollectionView.layoutIfNeeded()
})

Automatically update preferredContentSize upon rotation

I am looking for a way to enforce a UIPopoverPresentationController automatically resize to fill most of the screen, minus say 50 units of padding.
I first tried settings the preferredContentSize in viewDidLoad, and then detect when rotation occurs and update that CGSize to the new size I could calculate. But for some reason, this isn't working in my context. I was using UIDeviceOrientationDidChangeNotification which normally calls the method, but it's not called when the popover exists within a photo editing extension.
I am wondering if there's any other way to change the popover size besides manually updating the preferredContentSize after detecting a size change? If not, is there a way to detect rotation from within this popover in this context?
Try this:
func isLandscape() -> Bool {
// your logical here
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
let myNewSize = isLandscape() ? mySizeLandscape : mySizePortrait
self.preferredContentSize = myNewSize
super.viewWillTransition(to: myNewSize, with: coordinator)
}

Resources