Today Extension with UICollectionView different behaviour compared to Single View Application
I solved my own problem with the help of this answer. The solution so far is that I just setup the collection view in viewDidAppear(_:)
instead of viewDidLoad()
in the TodayViewController of the Today Extension (The space to the bottom currently looks like that, because I set a fix height of 220):
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var collectionView: UICollectionView!
let sectionInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let itemsPerRow: CGFloat = 3
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
extensionContext?.widgetLargestAvailableDisplayMode = .expanded
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = sectionInsets
collectionView = UICollectionView(frame: view.frame, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
collectionView.backgroundColor = .white
view.addSubview(collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
cell.backgroundColor = .blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
// toggle height in case of more/less button event
if activeDisplayMode == .compact {
self.preferredContentSize = CGSize(width: 0, height: 110)
} else {
self.preferredContentSize = CGSize(width: 0, height: 220)
}
}
}
Now I get the same result as in the Single View Application:
Update:
Unfortunately the solution from above had still a not expected behaviour. When the Today Extension was in an expanded state before the build, it looks like the screenshot from above, like expected. The problem is when it's in a collapsed state before the build and I want to expand the extension, the result looks like this (the expanded part of the collection view is just cutted off):
The solution for this problem is so far to set the initial frame of the collection view to .zero
and set the frame in viewWillLayoutSubviews()
like this:
import UIKit
import NotificationCenter
class TodayViewController: UIViewController, NCWidgetProviding, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var collectionView: UICollectionView!
let sectionInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
let itemsPerRow: CGFloat = 3
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
extensionContext?.widgetLargestAvailableDisplayMode = .expanded
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = sectionInsets
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
collectionView.backgroundColor = .white
view.addSubview(collectionView)
}
override func viewWillLayoutSubviews() {
let frame = view.frame
collectionView?.frame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
cell.backgroundColor = .blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
let availableWidth = view.frame.width - paddingSpace
let widthPerItem = availableWidth / itemsPerRow
return CGSize(width: widthPerItem, height: widthPerItem)
}
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
// toggle height in case of more/less button event
if activeDisplayMode == .compact {
self.preferredContentSize = CGSize(width: 0, height: 110)
} else {
self.preferredContentSize = CGSize(width: 0, height: 220)
}
}
}
Now the behaviour is like expected, like shown in the first result screenshot even if the Today Extension was first collapsed.
How can I add different contents in today extension?
You can adjust your xib of today extension and design what ever you need.
Note: today extension is only for limited features of application.
Don’t force your entire application features to today extension
the behavior of the UICollectionViewFlowLayout is not defined, because the cell width is greater than collectionView width
This happens when your collection view resizes to something less wide (go from landscape to portrait mode, for example), and the cell becomes too large to fit.
Why is the cell becoming too large, as the collection view flow layout should be called and return a suitable size ?
collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath)
Update to include Swift 4
@objc override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{ ... }
This is because this function is not called, or at least not straight away.
What happens is that your collection view flow layout subclass does not override the shouldInvalidateLayoutForBoundsChange
function, which returns false
by default.
When this method returns false, the collection view first tries to go with the current cell size, detects a problem (which logs the warning) and then calls the flow layout to resize the cell.
This means 2 things :
1 - The warning in itself is not harmful
2 - You can get rid of it by simply overriding the shouldInvalidateLayoutForBoundsChange
function to return true.
In that case, the flow layout will always be called when the collection view bounds change.
UICollectionView is not working in custom keyboard extension
The collection view will be visible when you provide constraints for the collectionView.
Code :
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(collectionView)
collectionView.topAnchor.constraintEqualToAnchor(topLayoutGuide.bottomAnchor,constant: 0.0).active = true
collectionView.bottomAnchor.constraintEqualToAnchor(bottomLayoutGuide.topAnchor, constant: 0.0).active = true
collectionView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
collectionView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
Dynamically setting layout on UICollectionView causes inexplicable contentOffset change
I have been pulling my hair out over this for days and have found a solution for my situation that may help.
In my case I have a collapsing photo layout like in the photos app on the ipad. It shows albums with the photos on top of each other and when you tap an album it expands the photos. So what I have is two separate UICollectionViewLayouts and am toggling between them with [self.collectionView setCollectionViewLayout:myLayout animated:YES]
I was having your exact problem with the cells jumping before animation and realized it was the contentOffset
. I tried everything with the contentOffset
but it still jumped during animation. tyler's solution above worked but it was still messing with the animation.
Then I noticed that it happens only when there were a few albums on the screen, not enough to fill the screen. My layout overrides -(CGSize)collectionViewContentSize
as recommended. When there are only a few albums the collection view content size is less than the views content size. That's causing the jump when I toggle between the collection layouts.
So I set a property on my layouts called minHeight and set it to the collection views parent's height. Then I check the height before I return in -(CGSize)collectionViewContentSize
I ensure the height is >= the minimum height.
Not a true solution but it's working fine now. I would try setting the contentSize
of your collection view to be at least the length of it's containing view.
edit:
Manicaesar added an easy workaround if you inherit from UICollectionViewFlowLayout:
-(CGSize)collectionViewContentSize { //Workaround
CGSize superSize = [super collectionViewContentSize];
CGRect frame = self.collectionView.frame;
return CGSizeMake(fmaxf(superSize.width, CGRectGetWidth(frame)), fmaxf(superSize.height, CGRectGetHeight(frame)));
}
CollectionView reloadData() weird behaviour
I am working on Fitness App which has pretty similar working CollectionView behavior for it's training plan section. Basically you select certain cell when you finish an exercise. So I played around a lot with way of implementing selecting and deselecting cells. As I see you are missing selecting and deselecting functions of CollectionViewDelegate. There you set different states Effects
Here is the functions you have to use:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.showIcon()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? CustomCollectionViewCell {
cell.hideIcon()
}
}
Related Topics
How Is This Slide-Up Menu from the iPhone Messages App Implemented
How Do Ruler Apps Stay Accurate on All Devices
Resize Inputaccessoryview Dynamically in iOS 8
Change Ncwidgetdisplaymode Programmatically in iOS10 Widget
Handle Multiple File (Image) Uploads to Aws S3 Swift
Swift Select All Photos from Specific Photos Album
Using Cloud Code with the Parse Server and Heroku
Dismiss Keyboard with Swipe Gesture
Swift iOS - Tag Collection View
Show Two Different Custom Cells in Same Uitableview - Swift Firebase