Search code examples
swiftviewuicollectionviewprogrammatically-created

Programmatically placing collectionView in view


I want to place a collectionView programmatically between a navigationBar, a label and the tabBar on the bottom.

I do not want to have any spacing between the different parts, how do I correctly ad the views to the view? Whats the best way to do it?

enter image description here

import UIKit

private let reUseIdentifierCollectionView = "CollectionViewCell1"

class CVControllerViewController: UIViewController {


    var heightNavigationBarTop: CGFloat{
        get{
            let barHeight = self.navigationController?.navigationBar.frame.height ?? 0
            let statusBarHeight = UIApplication.shared.isStatusBarHidden ? CGFloat(0) : UIApplication.shared.statusBarFrame.height
            return barHeight + statusBarHeight
        }
    }
    //Here I am getting the SafeArea above the NavigationBar
    var topSafeArea: CGFloat{
        get{
            var topSafeArea: CGFloat
            if #available(iOS 11.0, *) {
                topSafeArea = view.safeAreaInsets.top
            } else {
                topSafeArea = topLayoutGuide.length

            }
            return topSafeArea
        }
    }

    var tabBarHeight: CGFloat{ get{ return (self.tabBarController?.tabBar.frame.height)! } }

    lazy var label: UILabel = {
        let lb = UILabel()
        lb.frame = CGRect(x: 0, y: heightNavigationBarTop + topSafeArea, width: view.frame.width, height: 50)
        lb.backgroundColor = .red
        return lb
    }()

    var collectionView: UICollectionView!
    var content = [Int: Any]()

    override func viewDidLoad() {
        super.viewDidLoad()
        loadContent()
        loadCollectionView()
        view.addSubview(label)
        view.addSubview(collectionView)
    }


    func loadCollectionView() {
        let layout = UICollectionViewFlowLayout()
        let frame = CGRect(x: 0, y: heightNavigationBarTop + label.frame.height + topSafeArea, width: self.view.frame.height, height: self.view.frame.height - (heightNavigationBarTop + label.frame.height + tabBarHeight + topSafeArea))
        collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
        collectionView.backgroundColor = .white
        collectionView.register(CoinCVCCell.self, forCellWithReuseIdentifier: reUseIdentifierCollectionView)
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.scrollDirection = .horizontal
            flowLayout.minimumLineSpacing = 0
        }
        collectionView.delegate   = self
        collectionView.dataSource = self
    }

    func loadContent() {
        content[0] = Content(name: "Blau", color: .blue)
        content[1] = Content(name: "Grün", color: .green)
        content[2] = Content(name: "Gelb", color: .yellow)
        content[3] = Content(name: "brown", color: .brown)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

extension CVControllerViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let outputCell: UICollectionViewCell
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reUseIdentifierCollectionView, for: indexPath) as! CoinCVCCell
        cell.content = (content[indexPath.item] as! Content)
        outputCell = cell
        return outputCell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = view.frame.width
        let height = view.frame.height - (heightNavigationBarTop + label.frame.height + topSafeArea + tabBarHeight)

        let output = Utility.shared.CGSizeMake(width, height)
        return output
    }
}


class BaseCell: UICollectionViewCell {
    override init(frame: CGRect) {
        super .init(frame: frame)
        setUpViews()
    }
    func setUpViews() {

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

class CoinCVCCell: BaseCell {
    var content: Content? {
        didSet{
            backgroundColor = content?.color
        }
    }
}

class Content {
    var name: String?
    var color: UIColor?
    init(name: String, color: UIColor) {
        self.name = name
        self.color = color
    }
}

final class Utility: NSObject {
    private override init() { }
    static let shared = Utility()

    func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
        return CGRect(x: x, y: y, width: width, height: height)
    }

    func CGSizeMake( _ width:CGFloat, _ height:CGFloat) -> CGSize{
        return CGSize(width: width, height: height)
    }
}

I do not understand why the collectionView does not fit in to the space, and why the margins occur, because I subtract the height of the different elements on the view from the total height of the view.

EDIT:

private let reUseIdentifierCollectionView = "CollectionViewCell1"

class CVControllerViewController: UIViewController {


    var heightNavigationBarTop: CGFloat{
        get{
            let barHeight = self.navigationController?.navigationBar.frame.height ?? 0
            let statusBarHeight = UIApplication.shared.isStatusBarHidden ? CGFloat(0) : UIApplication.shared.statusBarFrame.height
            return barHeight + statusBarHeight
        }
    }
    //Here I am getting the SafeArea above the NavigationBar
    var topSafeArea: CGFloat{
        get{
            var topSafeArea: CGFloat
            if #available(iOS 11.0, *) {
                topSafeArea = view.safeAreaInsets.top
            } else {
                topSafeArea = topLayoutGuide.length
            }
            return topSafeArea
        }
    }

    var tabBarHeight: CGFloat{ get{ return (self.tabBarController?.tabBar.frame.height)! } }

    lazy var label: UILabel = {
        let lb = UILabel()
        //lb.frame = CGRect(x: 0, y: heightNavigationBarTop + topSafeArea, width: view.frame.width, height: 50)
        lb.backgroundColor = .red
        return lb
    }()

    var collectionView: UICollectionView!
    var content = [Int: Any]()

    override func viewDidLoad() {
        super.viewDidLoad()
        loadContent()
        loadCollectionView()
        setLayout()
//        view.addSubview(label)
//        view.addSubview(collectionView)
    }

    func setLayout(){
        view.sv(label, collectionView)
        view.layout(
            heightNavigationBarTop,
            |-label-| ~ 80,
            0,
            |collectionView|,
            tabBarHeight
        )
    }

    func loadCollectionView() {
        let layout = UICollectionViewFlowLayout()
        let frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
        collectionView = UICollectionView(frame: frame, collectionViewLayout: layout)
        collectionView.backgroundColor = .white
        collectionView.register(CoinCVCCell.self, forCellWithReuseIdentifier: reUseIdentifierCollectionView)
        collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
        if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            flowLayout.scrollDirection = .horizontal
            flowLayout.minimumLineSpacing = 0
        }
        collectionView.delegate   = self
        collectionView.dataSource = self
    }


    func loadContent() {
        content[0] = Content(name: "Blau", color: .blue)
        content[1] = Content(name: "Grün", color: .green)
        content[2] = Content(name: "Gelb", color: .yellow)
        content[3] = Content(name: "brown", color: .brown)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

extension CVControllerViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 4
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let outputCell: UICollectionViewCell
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reUseIdentifierCollectionView, for: indexPath) as! CoinCVCCell
        cell.content = (content[indexPath.item] as! Content)
        outputCell = cell
        return outputCell
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = view.frame.width
        let height = view.frame.height - (tabBarHeight + label.frame.height + heightNavigationBarTop)

        let output = Utility.shared.CGSizeMake(width, height)
        return output
    }
}

I am using the framework Stevia, basically what sv does is setting an subview and setting "translatesAutoresizingMaskIntoConstraints" to false. With layout I am setting the constraints, the solution now does what I want but is that the way you recommended?

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) ->

Here I still use the frame, does that make sense?


Solution

  • because I subtract the height of the different elements on the view from the total height of the view

    But you don’t know the total height of the view. That’s the problem.

    You are calling loadCollectionView in viewDidLoad. That is way too early, because nothing has its correct frame yet. Therefore all your calculations to calculate the frame of the collection view, based on the frame of other things, are incorrect.

    You could solve this by waiting until viewDidLayoutSubviews to call loadCollectionView, but don't. You shouldn't be calculating frames at all! Instead, do everything with autolayout and let the autolayout engine do the layout for you. That way, you can do the construction in viewDidLoad without any frame values coming into play.