Search code examples
iosswiftuicollectionviewswift3today-extension

Today Extension with UICollectionView different behaviour compared to Single View Application


I try to add a collection view to a Today Extension with specifying the items per row of 3 items and also with setting the section insets for with 20 for top, left, bottom and right. When I do this in a Single View Application everything is like expected, but when I do the same programmatically for the Today Extension the collection view looks different, especially the space of the right and bottom seems not to be like in the Single View Application. What's the reason why there is this difference? I expected the same behaviour for the Today Extension like in the Single View Application.

To understand my problem better below is the code of the Single View Application and the Today Extension, both with Screenshots:

ViewController of the Single View Application:

import UIKit

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    var collectionView: UICollectionView!
    let sectionInsets = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
    let itemsPerRow: CGFloat = 3

    override func viewDidLoad() {
        super.viewDidLoad()

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

Screenshot of the Single View Application:

enter image description here

TodayViewController of the Today Extension:

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 viewDidLoad() {
        super.viewDidLoad()

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

Screenshot of the Today Extension:

enter image description here


Solution

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

    enter image description here

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

    enter image description here

    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.