Today Extension with Uicollectionview Different Behaviour Compared to Single View Application

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:

Sample Image

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):

Sample Image

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



Leave a reply



Submit