UICollectionView's background is a screenshot of the initial position of the scrollview? - ios

This seems like a weird bug and I have adapted the code to see the bug better. By default the background color of my UICollectionView seems to be .systemBackground, but when I set it to .clear, instead of having a clear background, I have a "ghost" of the starting position of the first items in the scroll as a abckground. What could be happening?
The functions of the UICollectionView protocol:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 30
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "LineCell", for: indexPath) as! LineCell
cell.changeSize(indexPath.row)
cell.text.text = String(indexPath.row)
return cell
}
How I'm adding the view:
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.estimatedItemSize = CGSize(width: 50, height: 80)
numberPicker = UICollectionView(frame: .zero, collectionViewLayout: layout)
secondsControlHolder.addSubview(numberPicker!)
numberPicker?.delegate = delegate
numberPicker?.dataSource = delegate
numberPicker?.register(LineCell.self, forCellWithReuseIdentifier: "LineCell")
numberPicker?.translatesAutoresizingMaskIntoConstraints = false
numberPicker?.heightAnchor.constraint(equalToConstant: 120).isActive = true
numberPicker?.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor).isActive = true
numberPicker?.leadingAnchor.constraint(equalTo: secondsControlHolder.leadingAnchor).isActive = true
numberPicker?.trailingAnchor.constraint(equalTo: secondsControlHolder.trailingAnchor).isActive = true
numberPicker?.backgroundColor = .clear
numberPicker?.showsVerticalScrollIndicator = false
numberPicker?.showsHorizontalScrollIndicator = false
numberPicker?.isPagingEnabled = false
numberPicker?.contentInset = UIEdgeInsets(top: 0, left: (delegate.view.frame.size.width)/2, bottom: 0, right: (delegate.view.frame.size.width)/2)
let arrow = UIView()
let arrowTriangle = UIImageView(image: UIImage(systemName: "arrowtriangle.up.fill"))
secondsControlHolder.addSubview(arrow)
secondsControlHolder.addSubview(arrowTriangle)
arrow.translatesAutoresizingMaskIntoConstraints = false
arrow.bottomAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -10).isActive = true
arrow.centerXAnchor.constraint(equalTo: secondsControlHolder.centerXAnchor).isActive = true
arrow.heightAnchor.constraint(equalToConstant: 100).isActive = true
arrow.backgroundColor = .label
arrow.widthAnchor.constraint(equalToConstant: 5).isActive = true
arrow.isUserInteractionEnabled = false
arrowTriangle.translatesAutoresizingMaskIntoConstraints = false
arrowTriangle.centerXAnchor.constraint(equalTo: arrow.centerXAnchor).isActive = true
arrowTriangle.topAnchor.constraint(equalTo: secondsControlHolder.bottomAnchor, constant: -130).isActive = true
arrowTriangle.tintColor = .label
arrowTriangle.heightAnchor.constraint(equalToConstant: 23).isActive = true
arrowTriangle.widthAnchor.constraint(equalToConstant: 30).isActive = true
The LineCell class:
import UIKit
class LineCell: UICollectionViewCell {
let lineHolder = UIView()
let text = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
text.text = "test"
text.textColor = .white
lineHolder.translatesAutoresizingMaskIntoConstraints = false
lineHolder.addSubview(text)
text.translatesAutoresizingMaskIntoConstraints = false
text.topAnchor.constraint(equalTo: lineHolder.topAnchor).isActive = true
text.bottomAnchor.constraint(equalTo: lineHolder.bottomAnchor).isActive = true
text.leadingAnchor.constraint(equalTo: lineHolder.leadingAnchor).isActive = true
text.trailingAnchor.constraint(equalTo: lineHolder.trailingAnchor).isActive = true
addSubview(lineHolder)
// layer.backgroundColor = CGColor(red: 100, green: 100, blue: 100, alpha: 1)
// setupLineHolder()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupLineHolder(){
lineHolder.frame = CGRect(x: 0, y: 0, width: 1, height: 80)
lineHolder.backgroundColor = .label
// setupLine()
}
func changeSize(_ index: Int){
if index % 5 == 0 {
lineHolder.frame = CGRect(x: 0, y: 0, width: 50, height: 80)
lineHolder.backgroundColor = .label
}else{
lineHolder.frame = CGRect(x: 0, y: 15, width: 50, height: 50)
lineHolder.backgroundColor = .label.withAlphaComponent(0.7)
}
}
}

Turns out I was calling setupControlsArea() twice. It wasn't just the UICollectionView that was duplicated, but the whole controls area view.

Related

UIImageView not resizing as circle and UILabel not resizing within StackView and Custom Collection Cell

I am trying to resize my UIImageView as a circle, however; every time I try to resize the UIImageView, which is inside a StackView along with the UILabel, I keep on ending up with a more rectangular shape. Can someone show me where I am going wrong I have been stuck on this for days? Below is my code, and what it's trying to do is add the image with the label at the bottom, and the image is supposed to be round, and this is supposed to be for my collection view controller.
custom collection view cell
import UIKit
class CarerCollectionViewCell: UICollectionViewCell {
static let identifier = "CarerCollectionViewCell"
private let imageView: UIImageView = {
let imageView = UIImageView()
imageView.frame = CGRect(x: 0, y: 0, width: 20, height: 20);
//imageView.center = imageView.superview!.center;
imageView.contentMode = .scaleAspectFill
imageView.layer.borderWidth = 4
imageView.layer.masksToBounds = false
imageView.layer.borderColor = UIColor.orange.cgColor
imageView.layer.cornerRadius = imageView.frame.height / 2
return imageView
}()
private let carerNamelabel: UILabel = {
let carerNamelabel = UILabel()
carerNamelabel.layer.masksToBounds = false
carerNamelabel.font = .systemFont(ofSize: 12)
carerNamelabel.textAlignment = .center
carerNamelabel.layer.frame = CGRect(x: 0, y: 0, width: 50, height: 50);
return carerNamelabel
}()
private let stackView: UIStackView = {
let stackView = UIStackView()
stackView.layer.masksToBounds = false
stackView.axis = .vertical
stackView.alignment = .center
stackView.backgroundColor = .systemOrange
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
return stackView
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureContentView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func configureContentView() {
imageView.clipsToBounds = true
stackView.clipsToBounds = true
carerNamelabel.clipsToBounds = true
contentView.addSubview(stackView)
configureStackView()
}
private func configureStackView() {
allContraints()
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(carerNamelabel)
}
private func allContraints() {
setStackViewConstraint()
}
private func setStackViewConstraint() {
stackView.widthAnchor.constraint(equalTo: contentView.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: contentView.heightAnchor).isActive = true
stackView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
public func configureImage(with imageName: String, andImageName labelName: String) {
imageView.image = UIImage(named: imageName)
carerNamelabel.text = labelName
}
override func layoutSubviews() {
super.layoutSubviews()
stackView.frame = contentView.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
carerNamelabel.text = nil
}
}
Below here is my code for the CollectionViewControler
custom collection view
import UIKit
class CarerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .vertical
layout.itemSize = CGSize(width: 120, height: 120)
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(CarerCollectionViewCell.self, forCellWithReuseIdentifier: CarerCollectionViewCell.identifier)
collectionView.showsVerticalScrollIndicator = false
collectionView.backgroundColor = .clear
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
// Layout constraints for `collectionView`
NSLayoutConstraint.activate([
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor),
collectionView.heightAnchor.constraint(lessThanOrEqualTo: view.heightAnchor, constant: 600),
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor)
])
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CarerCollectionViewCell.identifier, for: indexPath) as! CarerCollectionViewCell
cell.configureImage(with: "m7opt04g_ms-dhoni-afp_625x300_06_July_20", andImageName: "IMAGE NO. 1")
return cell
}
}
Can somebody show or point to me what I am doing wrong, thank you
This is what I am trying to achieve
UIStackView could be tricky sometimes, you can embed your UIImageView in a UIView, and move your layout code to viewWillLayoutSubviews(), this also implys for the UILabel embed that also inside a UIView, so the containers UIViews will have a static frame for the UIStackViewto be layout correctly and whats inside them will only affect itself.

errors with UICollectionViewFlowLayout scrolling

I am setting up a UiCollectionView and I am trying to have the view scroll horizontally. The View is setup up and I can see labels and images but it does not scroll at all. The height of the collectionViewCell itself is 350 and how i am setting up the cell is
let SubView: UIView = {
let view = UIView()
view.backgroundColor = GREEN_Theme
view.layer.cornerRadius = 10
return view
}()
let headerLabel: UILabel = {
let label = UILabel()
label.text = "label"
label.font = UIFont.systemFont(ofSize: 30)
label.font = .boldSystemFont(ofSize: 30)
label.numberOfLines = 0
label.textColor = UIColor.black
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
addSubview(SubView)
SubView.anchors(top: topAnchor, topPad: 0, bottom: nil, bottomPad: 0, left: leftAnchor, leftPad: 0, right: rightAnchor, rightPad: 0, height: 1, width: self.bounds.width - 20)
addSubview(headerLabel)
headerLabel.anchors(top: SubView.bottomAnchor, topPad: 5 , bottom: nil, bottomPad: 0, left: safeAreaLayoutGuide.leftAnchor, leftPad: 15, right: nil, rightPad: 0, height: 50, width: 200)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let recentCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
func setupViews() {
backgroundColor = UIColor.clear
addSubview(recentCollectionView)
contentView.isUserInteractionEnabled = true
recentCollectionView.dataSource = self
recentCollectionView.delegate = self
recentCollectionView.isUserInteractionEnabled = true
recentCollectionView.backgroundColor = UIColor.blue
recentCollectionView.register(recentCell.self, forCellWithReuseIdentifier: cellID)
recentCollectionView.layer.zPosition = 1
recentCollectionView.topAnchor.constraint(equalTo: topAnchor, constant: 40).isActive = true
recentCollectionView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true
recentCollectionView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
recentCollectionView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -40).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return reviews.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! recentCell
cell.configureCell(reviews[indexPath.row])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: frame.height - 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 100, left: 14, bottom: 0, right: 14)
}
}
class recentCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
let imageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "rake")
iv.contentMode = .scaleAspectFill
iv.layer.cornerRadius = 16
iv.layer.masksToBounds = true
return iv
}()
let nameLabel: UILabel = {
let label = UILabel()
label.text = "Zach Wilcox"
label.font = UIFont.systemFont(ofSize: 14)
label.numberOfLines = 2
return label
}()
let categoryLabel: UILabel = {
let label = UILabel()
label.text = "Yard Work"
label.font = UIFont.systemFont(ofSize: 13)
label.textColor = UIColor.darkGray
return label
}()
let starControls = starStackView()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
addSubview(imageView)
addSubview(nameLabel)
addSubview(categoryLabel)
addSubview(starControls)
starControls.translatesAutoresizingMaskIntoConstraints = false
starControls.distribution = .fillEqually
imageView.frame = CGRect(x:0, y: 0, width: frame.width, height: frame.width)
nameLabel.anchors(top: imageView.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
categoryLabel.anchors(top: nameLabel.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
starControls.anchors(top: categoryLabel.bottomAnchor, topPad: 1, bottom: nil, bottomPad: 0, left: imageView.leftAnchor, leftPad: 0, right: nil, rightPad: 0, height: 20, width: 100)
}
In my console, I see
the item height must be less than the height of the UICollectionView minus the section insets top and bottom values, minus the content insets top and bottom values.
I have tried to set the background colors to blue in the recentCollectionView and the collectionView itself to see if the if the height of the cell is exceeding the height of the collectionView but it is not.
images below,
the first image, is the size of the entire contentView.
and this image is the size of the recentCollectionView
as we can see with the images, the recentCollectionView is not larger than the collectionView. Why am I seeing this error and why will my collectionViewFlowLayout not scroll?
I have found an answer shortly after posting my question but did not want to delete just in case some other newbie had the same problem. All I had to do was add the recentCollectionView to the contentView's subview not simply just addSubview.
contentView.addSubview(recentCollectionView)
super simple fix.

How to fix wrong indexPath returned by didSelectRowAt?

I have a UITableView; without a tableHeaderView tapping a row and triggering didSelectRowAt returns the correct index path.
When I set the tableHeaderView property, didSelectRowAt either does not fire or returns the tappedRow + 2. Where am I going wrong?
Here is my code
class MenuController: UIViewController {
// Mark -- Properties
var tableView: UITableView!
var delegate: HomeControllerDelegate?
var headerView: HeaderView? = nil
var user: User? = nil
// Mark -- Init
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
if let user = self.user {
populateMenuHeader(email: user.email, firstName: user.firstName, lastName: user.lastName, imageUrl: user.imageUrl)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//updateHeaderViewHeight(for: tableView.tableHeaderView)
}
func updateHeaderViewHeight(for header: UIView?) {
guard let headerView = headerView else { return }
headerView.frame.size.height = 170
}
func populateMenuHeader(email: String, firstName: String, lastName: String, imageUrl: String) {
headerView?.emailLabel?.text = email
headerView?.nameLabel?.text = "\(firstName) \(lastName)"
let request = ImageRequest(
url: URL(string: imageUrl)!,
processors: [
ImageProcessor.Resize(size: CGSize(width: 70, height: 70)),
ImageProcessor.Circle()
]
)
Nuke.loadImage(with: request, into: headerView!.imageView!)
}
// Mark -- Handlers
func configureTableView() {
// Create Material Header
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
//headerView?.heightAnchor.constraint(equalToConstant: 170).isActive = true
headerView?.translatesAutoresizingMaskIntoConstraints = false
tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.tableHeaderView = headerView
tableView.sectionHeaderHeight = 170
tableView.register(MenuOptionCell.self, forCellReuseIdentifier: reuseIdentifier)
tableView.backgroundColor = .darkGray
tableView.separatorStyle = .none
tableView.rowHeight = 80
view.addSubview(tableView)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
}
}
extension MenuController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! MenuOptionCell
let menuOption = MenuOption(rawValue: indexPath.row)
cell.descriptionLabel.text = menuOption?.description
cell.iconImageView.image = menuOption?.image
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
print("tapped row: \(row)")
let menuOption = MenuOption(rawValue: row)
delegate?.handleMenuToggle(forMenuOption: menuOption)
}
}
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
if let context = UIGraphicsGetCurrentContext() {
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(1)
context.move(to: CGPoint(x: 0, y: bounds.height))
context.addLine(to: CGPoint(x: bounds.width, y: bounds.height))
context.strokePath()
}
}
}
class HeaderView : UIView {
var imageView: UIImageView? = nil
var nameLabel: UILabel? = nil
var emailLabel: UILabel? = nil
override init(frame: CGRect) {
super.init(frame: frame)
imageView = UIImageView()
imageView?.translatesAutoresizingMaskIntoConstraints = false
nameLabel = UILabel()
nameLabel?.translatesAutoresizingMaskIntoConstraints = false
nameLabel?.font = UIFont(name: "Avenir-Light", size: 20)
nameLabel?.text = "Test name"
nameLabel?.textColor = .white
emailLabel = UILabel()
emailLabel?.translatesAutoresizingMaskIntoConstraints = false
emailLabel?.textColor = .white
emailLabel?.font = UIFont(name: "Avenir-Light", size: 15)
emailLabel?.text = "testemail#gmail.com"
self.addSubview(imageView!)
self.addSubview(nameLabel!)
self.addSubview(emailLabel!)
let lineView = CustomView(frame: CGRect(x: 0, y: frame.height - 1, width: frame.width, height: 1))
self.addSubview(lineView)
imageView?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
imageView?.topAnchor.constraint(equalTo: topAnchor, constant: 20).isActive = true
imageView?.widthAnchor.constraint(equalTo: widthAnchor, constant: 70).isActive = true
imageView?.heightAnchor.constraint(equalTo: heightAnchor, constant: 70).isActive = true
nameLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
nameLabel?.topAnchor.constraint(equalTo: imageView!.bottomAnchor, constant: 10).isActive = true
emailLabel?.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
emailLabel?.topAnchor.constraint(equalTo: nameLabel!.bottomAnchor, constant: 5).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The problem appears to be due to the fact that you are not properly setting the height of the header view. The documentation for tableHeaderView states:
When assigning a view to this property, set the height of that view to a nonzero value. The table view respects only the height of your view's frame rectangle; it adjusts the width of your header view automatically to match the table view's width.
Update your header view code:
Change:
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
//headerView?.heightAnchor.constraint(equalToConstant: 170).isActive = true
headerView?.translatesAutoresizingMaskIntoConstraints = false
...
tableView.sectionHeaderHeight = 170
to just:
headerView = HeaderView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 170))
That's it. No need to mess with constraints. No need to set the unrelated section height. Just give the header view's frame the desired height.

Setting CollectionView cell height based on number of buttons

I have a collection view that will have a UILabel and between 2-5 UIButtons.
I want the cell to size to how many buttons that are visible according to each cell. I know that each button is about a 100 in height.
class myViewController: UIViewController {
var myCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 100, right: 0) // add spacing to the bottom
layout.itemSize = CGSize(width: self.view.frame.width, height: 300)
layout.scrollDirection = .vertical
layout.minimumLineSpacing = 20
layout.minimumInteritemSpacing = 20
myCollectionView=UICollectionView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height), collectionViewLayout: layout)
myCollectionView.delegate=self
myCollectionView.dataSource=self
myCollectionView.register(MyCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
myCollectionView.alwaysBounceVertical = false
myCollectionView.showsVerticalScrollIndicator = false
myCollectionView.translatesAutoresizingMaskIntoConstraints=false
myCollectionView.backgroundColor=UIColor.white
myCollectionView.isPagingEnabled = false
loadViews()
}
func loadViews() {
self.view.addSubview(myCollectionView)
myCollectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive=true
myCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive=true
myCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive=true
myCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive=true
}
}
Note that the above code block has layout.itemSize = CGSize(width: self.view.frame.width, height: 300) which works great for 3 buttons (3*100 = 300).
Then when setting up my cell class I create my buttons and then determine their visibility based off of a variable (at the bottom of this).
class MyCollectionViewCell: UICollectionViewCell {
var btn1: UIButton!
var btn2: UIButton!
var btn3: UIButton!
var btn4: UIButton!
var btn5: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
func setupViews() {
addSubview(lblQue)
lblQue.topAnchor.constraint(equalTo: self.topAnchor, constant: 30).isActive=true
lblQue.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 12).isActive=true
lblQue.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -12).isActive=true
lblQue.heightAnchor.constraint(equalToConstant: 50).isActive=true
let btnWidth: CGFloat = 650
let btnHeight: CGFloat = 65
btn1 = getButton(tag: 0)
addSubview(btn1)
NSLayoutConstraint.activate([btn1.topAnchor.constraint(equalTo: lblQue.bottomAnchor, constant: 10), btn1.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn1.widthAnchor.constraint(equalToConstant: btnWidth), btn1.heightAnchor.constraint(equalToConstant: btnHeight)])
btn1.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn2 = getButton(tag: 1)
addSubview(btn2)
NSLayoutConstraint.activate([btn2.topAnchor.constraint(equalTo: btn1.bottomAnchor, constant: 10), btn2.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn2.widthAnchor.constraint(equalToConstant: btnWidth), btn2.heightAnchor.constraint(equalToConstant: btnHeight)])
btn2.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn3 = getButton(tag: 2)
addSubview(btn3)
NSLayoutConstraint.activate([btn3.topAnchor.constraint(equalTo: btn2.bottomAnchor, constant: 10), btn3.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn3.widthAnchor.constraint(equalToConstant: btnWidth), btn3.heightAnchor.constraint(equalToConstant: btnHeight)])
btn3.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn4 = getButton(tag: 3)
addSubview(btn4)
NSLayoutConstraint.activate([btn4.topAnchor.constraint(equalTo: btn3.bottomAnchor, constant: 10), btn4.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn4.widthAnchor.constraint(equalToConstant: btnWidth), btn4.heightAnchor.constraint(equalToConstant: btnHeight)])
btn4.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
btn5 = getButton(tag: 4)
addSubview(btn5)
NSLayoutConstraint.activate([btn5.topAnchor.constraint(equalTo: btn4.bottomAnchor, constant: 10), btn5.leftAnchor.constraint(equalTo: self.centerXAnchor, constant: -300), btn5.widthAnchor.constraint(equalToConstant: btnWidth), btn5.heightAnchor.constraint(equalToConstant: btnHeight)])
btn5.addTarget(self, action: #selector(btnOptionAction), for: .touchUpInside)
}
func getButton(tag: Int) -> UIButton {
let btn=UIButton()
btn.tag=tag
btn.setTitle("Option", for: .normal)
btn.setTitleColor(UIColor.black, for: .normal)
btn.backgroundColor=UIColor.white
btn.layer.borderWidth=1
btn.layer.borderColor=UIColor.darkGray.cgColor
btn.layer.cornerRadius=5
btn.clipsToBounds=true
btn.translatesAutoresizingMaskIntoConstraints=false
return btn
}
let lblQue: UILabel = {
let lbl=UILabel()
lbl.text="This is a question and you have to answer it?"
lbl.textColor=UIColor.black
lbl.textAlignment = .center
lbl.font = UIFont.systemFont(ofSize: 20)
lbl.numberOfLines=4
lbl.translatesAutoresizingMaskIntoConstraints=false
return lbl
}()
var myVariable: MyClassIMade? {
didSet {
// go through and determine button Text and Visibility of each button
// i.e.
// if 1>0 {
// btn3.visible = false
// } else {
// btn3.visible = true
// }
}
}
So how can I determine how many buttons are visible to determine my size of cell for each section?
How about something like this ? just get all the buttons inside an array and then loop through them to check which button are hidden and which are not. you could use
.isHidden to hide and display them and then check which is hidden and return the number..
Example function :
func getButtonsCount(buttons: [UIButton]) -> Int{
//A counter to get how many button are not hidden
var count = Int()
//A Loop to check which buttons are hidden and increment the counter
for button in buttons {
if button.isHidden == true {
count += 1
}
}
return count
}
Here is how I ended up solving it:
I used sizeForItemAt which overrides all else and allowed me to get my cell for each row/usecase.
Basically, I can access all of the controls tied to my cell and determine their visibility and alter the height according to that.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! QuizCollectionViewCell
var countOfButtonsNotHidden = 0
if !cell.btn1.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn2.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn3.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn4.isHidden {
countOfButtonsNotHidden += 1
}
if !cell.btn5.isHidden {
countOfButtonsNotHidden += 1
}
return CGSize(width: self.view.frame.width, height: CGFloat(countOfButtonsNotHidden * 100))
}
Use a UIStackView to layout your buttons. When you set the buttons to .isHidden the stack view will automatically deal with the layout of the buttons.
Example:
let stackView = UIStackView(arrangedSubviews: [button1, button2, button3, button4, button5])
stackView.axis = .vertical
stackView.alignment = .fill
stackView.distribution = .equalSpacing
stackView.spacing = 10
Next, you can calculate the height of the collection view cell by multiplying the number of visible buttons with your set button height.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath) as! YourCellClass
let buttonHeight: CGFloat = 45
let numberOfVisibleButtons = 0
if !cell.button1.isHidden { numberOfVisibleButtons += 1 }
if !cell.button2.isHidden { numberOfVisibleButtons += 1 }
if !cell.button3.isHidden { numberOfVisibleButtons += 1 }
if !cell.button4.isHidden { numberOfVisibleButtons += 1 }
if !cell.button5.isHidden { numberOfVisibleButtons += 1 }
let cellHeight: CGFloat = (buttonHeight + cell.stackView.spacing) * numberOfVisibleButtons
return CGSize(width: yourCellWidth, height: cellHeight)
}

How do I add subviews to a custom UICollectionViewCell

I have a custom UICollectionViewCell class where I want to add subviews.
My cell class: The SetUpView() method will add all the subvies I need.
import Foundation
import UIKit
class RecipeCell: UICollectionViewCell {
var RecipeImg: UIImage!
var StarRatingImg: UIImage!
var RecipeTitleText = ""
var RecipeTextDescription = ""
var View: UIView!
var ImageContainer: UIImageView!
var FavIcon: UIImageView!
var StarRatingContainer: UIImageView!
var KCAL: UILabel!
var RecipeTitle: UITextView!
var RecipeText: UITextView!
func SetUpView()
{
//DropDown!.backgroundColor = UIColor.blueColor()
self.translatesAutoresizingMaskIntoConstraints = false
//View for recipe
View = UIView(frame: CGRectMake(0, 0, self.frame.width, self.frame.height))
View.backgroundColor = UIColor.whiteColor()
//Recipe image
ImageContainer = UIImageView(frame: CGRectMake(0, 0, View.frame.width, View.frame.height/2))
ImageContainer.image = RecipeImg
ImageContainer.contentMode = .ScaleToFill
//Recipe favorit icon
FavIcon = UIImageView(frame: CGRectMake(ImageContainer.frame.width - 35, 5, 30, 30))
FavIcon.image = UIImage(named: "LikeHeart")
//Star rating image
StarRatingContainer = UIImageView(frame: CGRectMake(10, ImageContainer.frame.height + 5, ImageContainer.frame.width - 20, (View.frame.height/2) * (1/5)))
StarRatingContainer.image = StarRatingImg
StarRatingContainer.contentMode = .ScaleAspectFit
//RecipeTitle container
RecipeTitle = UITextView(frame: CGRectMake(10, StarRatingContainer.frame.height + ImageContainer.frame.height + 10, View.frame.width - 20, 30))
RecipeTitle.font = UIFont(name: "OpenSans-Semibold", size: 12)
//RecipeTitle.backgroundColor = UIColor.redColor()
RecipeTitle.editable = false
RecipeTitle.text = RecipeTitleText
RecipeTitle.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
//RecipeText container
RecipeText = UITextView(frame: CGRectMake(10, StarRatingContainer.frame.height + ImageContainer.frame.height + RecipeTitle.frame.height + 15, View.frame.width - 20, 50))
RecipeText.font = UIFont(name: "OpenSans", size: 12)
//RecipeText.backgroundColor = UIColor.grayColor()
RecipeText.editable = false
RecipeText.text = RecipeTextDescription
RecipeText.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0)
//KCAL label
KCAL = UILabel(frame: CGRectMake(15, StarRatingContainer.frame.height + ImageContainer.frame.height + RecipeTitle.frame.height + RecipeText.frame.height + 20, 200, 20))
KCAL.text = "420 KCAL. PER. PORTION"
KCAL.font = UIFont(name: "OpenSans-Bold", size: 10)
KCAL.textColor = UIColor(CGColor: "#dc994a".CGColor)
//Adding the views
self.addSubview(View)
View.addSubview(ImageContainer)
View.addSubview(KCAL)
View.addSubview(StarRatingContainer)
View.addSubview(RecipeTitle)
View.addSubview(RecipeText)
ImageContainer.addSubview(FavIcon)
View.bringSubviewToFront(ImageContainer)
}
}
I have a UICollectionView which uses the custom cell class.
I create my UICollectionView in viewDidLoad()
// Create Collection view
layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
layout.itemSize = CGSize(width: screenWidth/MenuViewConst - 1, height: screenWidth - 1)
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
collectionView = UICollectionView(frame: CGRect(x: 0, y: 105, width: self.view.frame.width, height: self.view.frame.height - 150), collectionViewLayout: layout)
collectionView?.tag = 5
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(RecipeCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
collectionView!.backgroundColor = UIColor.lightGrayColor()
collectionView!.contentInset.top = 0
In cellForItemAtIndexPath delegate I set up the UICollectionView to use my custom cell class. But I can't call the SetUpView() method from my custom cell class here, because that will just keep adding subviews on subviews. I can't figure out how to add the subviews to the UICollectionViewCell before entering the delegate. Hope you guys can help - Thank you
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! RecipeCell
let recipe = self.RecipeArr[indexPath.row]
cell.backgroundColor = UIColor.grayColor()
cell.layer.borderColor = UIColor.whiteColor().CGColor
cell.layer.borderWidth = 0.5
cell.RecipeImg = UIImage(named: "Burger")
cell.StarRatingImg = UIImage(named: "StarRating")
cell.RecipeTitleText = recipe["name"].string!
cell.RecipeTextDescription = recipe["instruction"].string!
//BAD IDEA!
//cell.SetUpView()
print("new cell")
return cell
}
You need to use init(frame: CGRect) inherited function in the UICollectionViewCell .
class RecipeCell: UICollectionViewCell {
var imageView : UIImageView?
override init(frame: CGRect) {
super.init(frame: frame)
//initialize all your subviews.
imageView = UIImageView()
}
}
also don't forget to register your custom class in the viewDidLoad function
collectionView!.registerClass(RecipeCell.self, forCellWithReuseIdentifier: "CollectionViewCell")
and your collectionview delegate would be like this
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as! RecipeCell
cell.imageView.image = UIImage(named:"yourImage.png")
}

Resources