Swift: is it okay to add constraints to UICollectionview? - ios

I have this class
class HomePage: UIViewController,UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate
And in this class i have a lot of UIViews.One of which is tabView.Now i want to add my collectionView to the bottom of tabView,how can i do that? Here is my `collectionView
var flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: CGRect(x:0,y:500,width:self.view.frame.size.width,height:100 ), collectionViewLayout: flowLayout)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.backgroundColor = .red`
And constraints to tabView
tabView.topAnchor.constraint(equalTo: profileInfWrapper.bottomAnchor).isActive = true
tabView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tabView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width/4).isActive = true
tabView.heightAnchor.constraint(equalToConstant: 80).isActive = true

You can add the collectionView inside tabView and setup constraints:
collectionView.translatesAutoresizingMaskIntoConstraints = false
tabView.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: tabView.topAnchor, constant: 20).isActive = true
.... And add other constraints
Or you can add it under tab view like so:
self.view.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: tabView.bottomAnchor, constant: 20).isActive = true
__
BTW you can set tabView's width like so:
tabView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.25).isActive = true

Related

dynamic height of UICollectionView (horizontal scrolling) based on cell height

I've got the following situation:
A collectionView inside a containerView that sits below the navigationBar. The view is set up programmatically (no xib).
I want to place the containerView in a way that it does not need a height constraint. Is that possible? The containerView should get it's height based on the constraints in the collectionView cells.
Code:
This is the function which lays out the containerView inside the viewController:
private func setUpContainerView() {
view.addSubview(containerView)
containerView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20)
])
}
Inside the containerView (which is a subclass of UIView), the collectionView is set up like that (called in the init of that class):
private func setUpCollectionView() {
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: collectionViewFlowLayout())
guard let collectionView = collectionView else { return }
addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
collectionView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
collectionView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
collectionView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
collectionView.topAnchor.constraint(equalTo: self.topAnchor)
])
collectionView.delegate = self
collectionView.dataSource = self
registerCollectionViewNibs()
}
private func collectionViewFlowLayout() -> UICollectionViewFlowLayout{
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
return flowLayout
}
When I do it like that, the collectionView is not visible. It only shows up when I add a height constraint with a specific constant to the containerView (inside the setUpContainerView function).
The problem is, that I don't know that height. I would have to set that height to 10+10+50 = 70 manually. But I want to make the containerView get it's size based on the collectionView cell constraints. Is that possible?

How to load Xib to CollectionView using width constraint in Swift?

I have collectionView like this
I'm calling different 3 xib files to cells like this
collectionView.register(UINib(nibName: "OfficialVehicleHeader", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleHeaderCell")
collectionView.register(UINib(nibName: "OfficialVehicleMedium", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleMediumCell")
collectionView.register(UINib(nibName: "OfficialVehicleBottom", bundle: nil), forCellWithReuseIdentifier: "OfficialVehicleBottomCell")
My collectionView layout code
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
collectionView.setCollectionViewLayout(layout, animated: true)
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
In the xib file i set constraint (leading, trailing, bottom, top as 0) in addition height constraint. The height is running awesome no problem but i don't want to set width constraint. Because there is different between screens
Where did i mistake ?
You have to set leading and trailing it will set width according to devices width. But don't forget to make your collection views constraints to be resizable.
Here is my sample code on how to do it:
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
collectionView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 140).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
layout settings:
layout.itemSize = CGSize(width: self.view.frame.width - 10, height: self.view.frame.height / 3)
Cells constraints to be resizable:
cell.widthAnchor.constraint(equalTo: widthAnchor, constant: 0).isActive = true
cell.heightAnchor.constraint(equalTo: heightAnchor, constant: 0).isActive = true
cell.leftAnchor.constraint(equalTo: leftAnchor, constant: 0).isActive = true
cell.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
I solved the problem. I need to set width constraint but i don't want to set. I solve this way
Go to xib file and set width constraint
Go to size inspector and select that you created width constraint
Set identifier for example "myConstraint"
Open your cell file and define IBOutlet
for constraint in self.containerView.constraints {
if constraint.identifier == "myConstraint" {
constraint.constant = UIScreen.main.bounds.size.width * 0.70
}
}
containerView.layoutIfNeeded()
I added image that i mentioned

What is the difference of implementation between UITableView & UICollectionView when using Xib?

I try to implement TableView and CollectionView with Xib Files. I implement UITableview properly but when I try to implement ColletionView I get an error "libc++abi.dylib: terminating with uncaught exception of type NSException". I got this error in var collectionView = UICollectionView() line.
Why I dont get this error this line var tableView = UITableView() ? And where is my mistake ? And What is the solution ? Thanks in advance.
var tableView = UITableView()
var collectionView = UICollectionView()
override func viewDidLoad() {
super.viewDidLoad()
let headerMenu = UIView()
headerMenu.translatesAutoresizingMaskIntoConstraints = false
headerMenu.backgroundColor = .green
self.view.addSubview(headerMenu)
headerMenu.heightAnchor.constraint(equalToConstant: 48.0).isActive = true
headerMenu.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
headerMenu.topAnchor.constraint(equalTo: view.safeTopAnchor).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
headerMenu.addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: headerMenu.topAnchor, constant: 0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: -10).isActive = true
collectionView.rightAnchor.constraint(equalTo: headerMenu.rightAnchor, constant: -10).isActive = true
collectionView.leftAnchor.constraint(equalTo: headerMenu.leftAnchor, constant: 10).isActive = true
collectionView.register(UINib(nibName: "MenuCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MenuCollectionViewCell")
let mainView = UIView()
mainView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(mainView)
mainView.topAnchor.constraint(equalTo: headerMenu.bottomAnchor, constant: 0).isActive = true
mainView.bottomAnchor.constraint(equalTo: view.safeBottomAnchor, constant: 0).isActive = true
mainView.rightAnchor.constraint(equalTo: view.safeRightAnchor, constant: 0).isActive = true
mainView.leftAnchor.constraint(equalTo: view.safeLeftAnchor, constant: 0).isActive = true
mainView.backgroundColor = UIColor.white
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.estimatedRowHeight = UITableViewAutomaticDimension
mainView.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: mainView.topAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: mainView.bottomAnchor, constant: -10).isActive = true
tableView.rightAnchor.constraint(equalTo: mainView.rightAnchor, constant: -10).isActive = true
tableView.leftAnchor.constraint(equalTo: mainView.leftAnchor, constant: 10).isActive = true
tableView.separatorStyle = .none
tableView.register(UINib(nibName: "ProductTableViewCell", bundle: nil), forCellReuseIdentifier: "ProductTableViewCell")
}
You need to use other init to create UICollectionView instance:
let layout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: rect, collectionViewLayout: layout)
UICollectionView requires a UICollectionViewLayout in order to render. When creating a collection view via the storyboard this is already set with a default value.
You can initialise it like this:
let flowLayout = UICollectionViewFlowLayout()
let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: flowLayout)
Flow layout
A concrete layout object that organizes items into a grid with optional header and footer views for each section.
The flow layout is one of the default/built in options and is useful for creating a 'bookshelf' style grid in which the columns fill left to right and then top to bottom (unless you change the width).
There is a good tutorial on the Ray Wenderlich website which explains more about collection views and their layouts

Auto Layout using layout anchors programmatically not working

I am trying to add a stack view containing a UILabel at the top and a UICollectionView underneath. I am trying to constrain the stack view so that it takes up the full view, by anchoring it to all sides. When I run the app the UILabel appears and a slice of the collection view appears. The collection view says its width and height are both zero. Any help would be appreciated, thank you.
var collectionView: UICollectionView!
var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.view.translatesAutoresizingMaskIntoConstraints = false
let margins = self.view.layoutMarginsGuide
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
layout.itemSize = CGSize(width: self.collectionView.frame.width / 4, height: self.collectionView.frame.width / 4)
layout.minimumInteritemSpacing = self.collectionView.frame.width / 15
layout.minimumLineSpacing = self.collectionView.frame.width / 5
collectionView.backgroundColor = UIColor.black
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
titleLabel = UILabel()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 1
titleLabel.font = UIFont.systemFont(ofSize: 22)
titleLabel.backgroundColor = UIColor.lightGray
titleLabel.text = "Challenges"
titleLabel.textColor = UIColor.red
let stackView = UIStackView(arrangedSubviews: [titleLabel, collectionView])
stackView.backgroundColor = UIColor.white
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.alignment = .fill
stackView.spacing = 5
self.view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
//stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
//stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1.0).isActive = true
}
UPDATE:
I took my code and ran it in a new single view application and it worked as expected. Because of this I think it is worth it to mention that I am trying to incorporate this into a sprite kit game and I present the view controller by doing this:
let root = self.view?.window?.rootViewController
var viewC = SelectionCollectionViewController()
root?.present(viewC, animated: false, completion: nil)
Are there special steps I need to take because this is done with sprite kit?
I believe below screen shot is the expected output.
You might not need UIStackView , You can directly add it to self.view.
Once added to self.view, you can set up constraints. You can print the item Size in viewDidLayoutSubviews
class ViewController: UIViewController {
var collectionView: UICollectionView!
var titleLabel: UILabel!
let collectionCellIdentifier:String = "collectionCellId"
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.classForCoder(), forCellWithReuseIdentifier: collectionCellIdentifier)
collectionView.backgroundColor = UIColor.black
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
titleLabel = UILabel()
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 1
titleLabel.font = UIFont.systemFont(ofSize: 22)
titleLabel.backgroundColor = UIColor.lightGray
titleLabel.text = "Challenges"
titleLabel.textColor = UIColor.red
titleLabel.translatesAutoresizingMaskIntoConstraints = false
collectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(titleLabel)
self.view.addSubview(collectionView)
setUpConstraints()
}
func setUpConstraints(){
self.view.addConstraint(NSLayoutConstraint(item: self.titleLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier:0, constant:50.0 ))
titleLabel.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
titleLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
titleLabel.widthAnchor.constraint(equalTo: self.view.widthAnchor).isActive = true
collectionView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let flowLayout = (collectionView.collectionViewLayout as! UICollectionViewFlowLayout)
flowLayout.itemSize = CGSize(width: collectionView.frame.width / 4.0 , height: collectionView.frame.width / 4.0)
flowLayout.minimumInteritemSpacing = self.collectionView.frame.width / 15.0
flowLayout.minimumLineSpacing = self.collectionView.frame.width / 5.0
}
}
extension ViewController:UICollectionViewDataSource,UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionCellIdentifier, for: indexPath)
cell.backgroundColor = UIColor.gray
return cell
}
}
I think you need to know what happen in these functions:
viewDidLoad
viewWillAppear
viewWillLayoutSubviews
viewDidLayoutSubviews
viewDidAppear
if you use autoLayout, when in viewDidLoad(), the frame is not confirmed, because the view will make an auto adjustment when in viewWillLayoutSubviews() and viewDidLayoutSubviews(), so I suggest you make these code in viewDidAppear(), and then you may see what you want!
Furthermore, if you use storyboard or nib, you need to do these in awakeFromNib()

Disabling uicollectionview scroll makes uicollectionview smaller

I have got this uicollectionview
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 90
collectionView = UICollectionView(frame: CGRect(x:0,y:0,width:0,height:0), collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.isScrollEnabled = false
collectionView.heightAnchor.constraint(equalToConstant: collectionView.contentSize.height).isActive = true collectionView.topAnchor.constraint(equalTo:PostsTab.bottomAnchor).isActive = true
collectionView.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
collectionView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Now this collectionView is inside scrollView.On top of collectionview there is another view and it all looks like this
Now when i scroll down i want that UIView that is on top of collectionView to be left behind that's why i added isScrollEnabled = false,without that when i scroll down uiview stays on a fixed position
like this
override func viewDidAppear(_ animated: Bool) {
scrollView.contentSize = CGSize(width:1,height: 2400)
collectionView.heightAnchor.constraint(equalToConstant: collectionView.contentSize.height).isActive = true
}

Resources