1
votes

Using Swift-5.0.1, iOS-12.2,

I successfully was able to place a UICollectionView with screen-wide cells with the custom height of 150 pixels.

However, the navigationBar is hiding the first cell.

I saw different statements which state that a contentInset must be applied to the collectionView.

However, none of the posts I found clearly states the amount that has to be used as the offset (especially, considering all the new iPhone-screens and considering the fact that the navBar might be large or small).

I tried:

let offset1 = self.navigationController?.navigationBar.frame.size.height
let offset2 = navigationController?.navigationBar.frame.maxY       
self.edgesForExtendedLayout = []

self.collectionView.contentInset = UIEdgeInsets(top: offset2, left: 0, bottom: 0, right: 0)

But none of the values really match the needed inset !

My question: Is there a height-property of the navigationController (or other) that I can rely on in order to set the contentInset on the collectionView properly ??

I realised that for an iPhone XS, the correct inset is approximately 112 pixels. How does these come into place ??

The following images illustrate the NavBar hiding of the first cell of the collectionView.

enter image description here

My NavigationController and UICollectionViewController are instantiated inside AppDelegate.swift like this:

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        window = UIWindow(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        let vc = CollectionViewController()
        let nc = UINavigationController(rootViewController: vc)
        window?.rootViewController = nc
        window?.makeKeyAndVisible()

        return true
    }

Here the CollectionViewController:

import UIKit

class CollectionViewController: BaseListController, UICollectionViewDelegateFlowLayout {

    fileprivate let cellId = "cellId"
    fileprivate let activityIndicator = UIActivityIndicatorView()

    lazy var viewModel: PhotoListViewModel = {
        return PhotoListViewModel()
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Init the static view
        initView()

        // init view model
        initVM()
    }

    func initView() {
        self.navigationItem.title = "NavBar"
        collectionView.register(PhotoListCollectionViewCell.self, forCellWithReuseIdentifier: cellId)
        collectionView.backgroundColor = .white

        // a number of 112 makes the correct inset but why ???????
        collectionView.contentInset = UIEdgeInsets(top: 112, left: 0, bottom: 0, right: 0)
    }

    func initVM() {

        // Naive binding
        viewModel.showAlertClosure = { [weak self] () in
            DispatchQueue.main.async {
                if let message = self?.viewModel.alertMessage {
                    self?.showAlert( message )
                }
            }
        }

        viewModel.updateLoadingStatus = { [weak self] () in
            DispatchQueue.main.async {
                let isLoading = self?.viewModel.isLoading ?? false
                if isLoading {
                    self?.activityIndicator.startAnimating()
                    UIView.animate(withDuration: 0.2, animations: {
                        self?.collectionView.alpha = 0.0
                    })
                }else {
                    self?.activityIndicator.stopAnimating()
                    UIView.animate(withDuration: 0.2, animations: {
                        self?.collectionView.alpha = 1.0
                    })
                }
            }
        }

        viewModel.reloadTableViewClosure = { [weak self] () in
            DispatchQueue.main.async {
                self?.collectionView.reloadData()
            }
        }

        viewModel.initFetch()
    }

    func showAlert( _ message: String ) {
        let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
        alert.addAction( UIAlertAction(title: "Ok", style: .cancel, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return viewModel.numberOfCells
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as? PhotoListCollectionViewCell else {
            fatalError("Cell not exists in storyboard")
        }
        let cellVM = viewModel.getCellViewModel( at: indexPath )
        cell.photoListCellViewModel = cellVM

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: view.frame.width, height: 150)
    }

    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        self.viewModel.userPressed(at: indexPath)
        if viewModel.isAllowSegue {
            let detailVC = PhotoDetailViewController()
            if let photo = viewModel.selectedPhoto {
                detailVC.imageView = UIImageView()
                detailVC.imageView.contentMode = .scaleAspectFill
                detailVC.imageView.sd_setImage(with: URL(string: photo.image_url)) { (image, error, type, url) in

                }
            }
            self.navigationController?.pushViewController(detailVC, animated: true)
        }
    }
}

With its BaseController...

import UIKit

class BaseListController: UICollectionViewController {

    init() {
        super.init(collectionViewLayout: UICollectionViewFlowLayout())
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
1

1 Answers

0
votes

I finally found a solution.

With the fantastic help of Mykod, I was able to play around with the contentMode of the imageView being displayed inside the collectionViewCell as well as its anchor-constraints.

Here is the solution:

Inside PhotoListCollectionViewCell, you can set the contentMode as well as clipsToBounce:

var mainImageView: UIImageView = {
    let imgV = UIImageView()
    imgV.contentMode = .scaleAspectFill
    imgV.clipsToBounds = true
    return imgV
}()

And very importantly, you need to set the top-, leading-, trailing- and bottom-constraints equal to the cell's top-, leading-, trailing- and bottom constraints:


func setConstraints() {
    addSubview(mainImageView)
    mainImageView.translatesAutoresizingMaskIntoConstraints = false
    mainImageView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
    mainImageView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
    mainImageView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    mainImageView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
}

With the above changes, the imageView now starts right after the navBar (and is no longer lying behind it) - i.e. all correct now :)

You no longer need to use collectionView.contentInset = ... inside the viewDidLoad() of your CollectionViewController. The contentMode, clipsToBounds and correct anchoring makes the issue disappear.

enter image description here