I have problems using Custom UICollectionViewCell. Custom cell has one UILabel and one UIImageView. The problem is, when I put image on UIImageView(Left screenshot), UILabel in same cell disappears like picture below. But if there's no Image source on UIImageView, UILabel is not missing.
This is code for Viewcontroller which has UICollectionView
class CatTowerVeiwController: UIViewController {
#IBOutlet weak var popCountSwitch: UISwitch!
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
let switchState = UserDefaults.standard.bool(forKey: UserDataKey.popCountVisibility)
popCountSwitch.setOn(switchState, animated: false)
collectionView.register(UINib(nibName: "CatTowerCell", bundle: nil), forCellWithReuseIdentifier: "cell")
setupFlowLayout(numberOfCells: 2.0)
}
#IBAction func doneButtonClicked(_ sender: UIButton) {
let isLabelVisible = self.popCountSwitch.isOn
UserDefaults.standard.set(isLabelVisible, forKey: UserDataKey.popCountVisibility)
self.dismiss(animated: false)
}
}
extension CatTowerVeiwController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CatTowerCell
// cell.cellImage.image = #imageLiteral(resourceName: "popcat_closed")
cell.cellName.text = "Popcat"
print(cell.cellName.frame.size.height)
print(cell.cellName.frame.size.width)
return cell
}
}
extension CatTowerVeiwController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let leftAndRightPaddings: CGFloat = 45.0
let numberOfItemsPerRow: CGFloat = 2.0
let width = (collectionView.frame.width-leftAndRightPaddings)/numberOfItemsPerRow
return CGSize(width: width, height: width)
}
private func setupFlowLayout(numberOfCells: CGFloat) {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let halfWidth = UIScreen.main.bounds.width / numberOfCells
flowLayout.itemSize = CGSize(width: halfWidth * 0.8 , height: halfWidth * 0.8)
flowLayout.minimumInteritemSpacing = 0
flowLayout.minimumLineSpacing = halfWidth * 0.1
self.collectionView.collectionViewLayout = flowLayout
}
}
And this is code for custom cell
class CatTowerCell: UICollectionViewCell {
#IBOutlet weak var cellImage: UIImageView!
#IBOutlet weak var cellName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
This is what I trying to make:
The solution was Compression Priority
The height of UILabel became 0 because UIImageView is too big
My solution is setting Vertical compression Priority of UILabel larger than UIImageView.
Pin your imageview's bottom anchor constraint to the label's top anchor.
And make sure there are no conflicting constraints.
label.topAnchor.constraint(equalTo: imageview.bottomAnchor).isActive = true
Related
I want to ask. I make a custom menubar from collectionView and I want to link and change my menubar of collection view and the data from another collectionView while swiping. Here a picture what I'm try to make
but while try to swipe to left and right my menu bar is not following, I already make a reference to capture the value. but it's still not work. here is my code
class SegmentedView: UIView {
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
let knowledge = ["Pengetahuan", "Keterampilan", "Sikap"]
var selectedMenu: CGFloat?
}
extension SegmentedView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return knowledge.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! SegmentedCell
let item = knowledge[indexPath.item]
cell.nameLabel.text = item
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedMenu = CGFloat(indexPath.item) * frame.width
}
}
// This from my SegmentedViewController
class SegmentedViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var segmentedViews: SegmentedView!
let cellId = "assesmentCell"
let colors: [UIColor] = [UIColor.blue, UIColor.yellow, UIColor.green]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupCollection()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x / 3)
segmentedViews.selectedMenu = scrollView.contentOffset.x / 3
}
func setupCollection() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isPagingEnabled = true
}
}
thank you.
Your selectedMenu property does not listen for an event with the didSet property observer. So when you set selectedMenu in scrollViewDidScroll, nothing happens.
It may be a solution:
var selectedMenu: CGFloat? {
didSet {
collectionView.selectItem(at: <#T##IndexPath?#>, animated: <#T##Bool#>, scrollPosition: <#T##UICollectionView.ScrollPosition#>)
}
}
But be careful with the collectionView(_:didSelectItemAt:) method of SegmentedView, it can bring some unwanted behaviors. You can use a delegate pattern to trigger the method in another class.
ios version 11/12
Trying to make dynamic cell height for UIViewController (cell contains image with fixed height and label that can be multiline because of that label view-cell should be able dynamically change height). I'm using Storyboard to construct basic UI with some constrains that makes cell min width 300.
Getting strange UI behaviour during vertical scrolling in UIViewController. Some cells(that are not in the UI initially) resized to the size defined in Storyboard constrains and ignoring estimatedItemSize
import UIKit
class StartController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
let offers = SearchCategoryOffer.mockOffers
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.view.backgroundColor = UIColor.white
let titleImageView = NavigationImageView()
titleImageView.image = UIImage(named: "logo")
navigationItem.titleView = titleImageView
let w = self.collectionView.frame.width - 30
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
if let layout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInsetReference = .fromLayoutMargins
layout.sectionInset = UIEdgeInsetsMake(20, 5, 20, 5)
layout.minimumLineSpacing = 26
layout.minimumInteritemSpacing = 0
layout.estimatedItemSize = CGSize(width: w, height: 300) // cell size
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return offers.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: OfferCell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! OfferCell
cell.listTitle?.text = offers[indexPath.row].name
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let _: OfferCell = collectionView.cellForItem(at: indexPath) as! OfferCell
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
let _: OfferCell = collectionView.cellForItem(at: indexPath) as! OfferCell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// bla-bla-bla
}
}
class NavigationImageView: UIImageView {
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: 133, height: 35)
}
}
I have a UIcollectionview that allows scrolling the content horizontally only. Each UIcollectionviewCell is a custom view showing two labels one below the other. I have set constraints in the custom cell to position these labels. Irrespective of the screen width, I always want to show 7 UICollection cells. Is this possible?
You need to change itemSize by calculating from device width
let itemSpacing: CGFloat = 5
let itemsInOneLine: CGFloat = 7
let flow = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
flow.sectionInset = UIEdgeInsets(top: itemSpacing, left: itemSpacing, bottom: itemSpacing, right: itemSpacing)
flow.minimumInteritemSpacing = itemSpacing
flow.minimumLineSpacing = itemSpacing
let cellWidth = (UIScreen.main.bounds.width - (itemSpacing * 2) - ((itemsInOneLine - 1) * itemSpacing)) / itemsInOneLine
flow.itemSize = CGSize(width: cellWidth, height: 120)
Hope this is What you want,
Here is the code for this.
//
// ViewController.swift
// AssignmentSO
//
// Created by Anuradh Caldera on 4/24/19.
// Copyright © 2019 personal. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
private var mycollectionview: UICollectionView!
private let cellidentifier = "cellIdentifier"
private let minimuminterspace: CGFloat = 2
override func viewDidLoad() {
super.viewDidLoad()
setCollectionView()
}
}
extension ViewController {
fileprivate func setCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = minimuminterspace
layout.minimumInteritemSpacing = minimuminterspace
mycollectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
mycollectionview.translatesAutoresizingMaskIntoConstraints = false
mycollectionview.register(MyCell.self, forCellWithReuseIdentifier: cellidentifier)
mycollectionview.dataSource = self
mycollectionview.delegate = self
mycollectionview.backgroundColor = .white
mycollectionview.isPagingEnabled = true
// mycollectionview.contentInset = UIEdgeInsets(top: 0, left: minimuminterspace, bottom: 0, right: minimuminterspace)
view.addSubview(mycollectionview)
let collectionviewConstraints = [mycollectionview.leftAnchor.constraint(equalTo: view.leftAnchor, constant: minimuminterspace),
mycollectionview.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
mycollectionview.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -minimuminterspace),
mycollectionview.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height/4)]
NSLayoutConstraint.activate(collectionviewConstraints)
}
}
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellidentifier, for: indexPath) as! MyCell
cell.index = indexPath.item
cell.backgroundColor = .purple
return cell
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cellwidth = mycollectionview.frame.width/7 - minimuminterspace
let cellheight = mycollectionview.frame.height
return CGSize(width: cellwidth, height: cellheight)
}
}
class MyCell: UICollectionViewCell {
private var mainlabel: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
setLabel()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var index: Int! {
didSet {
mainlabel.text = "\(index+1)"
}
}
}
extension MyCell {
fileprivate func setLabel() {
mainlabel = UILabel()
mainlabel.translatesAutoresizingMaskIntoConstraints = false
mainlabel.textColor = .white
mainlabel.textAlignment = .center
addSubview(mainlabel)
let mainlabelConstraints = [mainlabel.centerXAnchor.constraint(equalTo: centerXAnchor),
mainlabel.centerYAnchor.constraint(equalTo: centerYAnchor)]
NSLayoutConstraint.activate(mainlabelConstraints)
}
}
Note: if you want to show 7 cells, then it is better to enable pagination. you can change the height as your need. hope this will help to someone. cheers!
Step 1 - Implement UICollectionViewDelegateFlowLayout delegate
Step 2 - Add below method
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionViewSize/7) , height: 50 )
}
Step 3 - pass size according to your requirement
Implement the collectionView layout delegate.
And divide the screen width in 7 parts as below.
please note following code is with respect to the collectionView width, considering the auto layout of collectionView width is set to screen margin.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.bounds.width/7, height:collectionView.bounds.height)
}
Though this will suffice your need, you will also need to handle the Cell's subviews to fit in the cell and display the content properly.
I'm trying to get 3 icons on the top bar.
import UIKit
class SectionHeader: UICollectionReusableView {
private let telButtonCellId = "TelButtonCell"
private let searchBarCellId = "SearchBarCell"
private let notificationButtonCellId = "NotificationButtonCell"
// MARK: - Properties
#IBOutlet weak var collectionView: UICollectionView!
// MARK: - Initialization
override func awakeFromNib() {
super.awakeFromNib()
self.collectionView.register(UINib(nibName:notificationButtonCellId, bundle: nil), forCellWithReuseIdentifier: notificationButtonCellId)
self.collectionView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width*0.2) // this is need to set the size of the collection view
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
extension SectionHeader : UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: notificationButtonCellId, for: indexPath) as! NotificationButtonCell
var image = UIImage(named: "globe")
cell.imageView.image=image?.addImagePaddingShift(x: 20 , y: 20,shiftX: 0, shiftY: 10)
cell.imageView.contentMode = .scaleAspectFit
cell.imageView.bounds=cell.bounds
cell.imageView.center = cell.center;
cell.backgroundColor = UIColor.lightGray
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let frameWidth = UIScreen.main.bounds.width
let frameHeight = UIScreen.main.bounds.width*0.2
return CGSize(width: frameWidth*0.15, height: frameHeight*1.0)
}
So only the first cell will have the globe displayed.
the rest of the 2 cells are empty even though i can change the background colour.
The SectionHeader is a nib
The imageView is subview of the cell, that needs to be with its size as I see. So it needs its frame (its position in relation to its superview coordinate system - the cell) to be set to the height and width of the cell with origin (0,0). The bounds of the cell provide these dimensions so the answer can be
cell.imageView.frame = cell.bounds
or better
cell.imageView.frame = CGRect(x: 0, y: 0, width: cell.frame.width, height: cell.frame.height)
Consider the following situation. I have an UICollectionView (inside UICollectionViewController), which looks almost the same as UITableView (the reason why I don't use UITalbeView is because I have non data views on layout, that I don't want to manage and mess with my IndexPath).
In order to achieve the autosizing cells I've set estimatedItemSize, something like that:
layout.estimatedItemSize = CGSize(width: self.view.bounds.size.width, height: 72)
Also, in my cell I have layout attributes:
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return layoutAttributes
}
So, by doing that I've got exact layout as UITableView with autosizing. And it works perfectly.
Now, I am trying to add the header and pin it on scrolling to the top of the section, like that:
layout.sectionHeadersPinToVisibleBounds = false
but layout goes into weird state, I have glitches all over the place, cells overlapping each other, and headers sometimes doesn't stick.
UPDATE:
The code of view controller and cell:
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
let layout = collectionView?.collectionViewLayout as! UICollectionViewFlowLayout
layout.sectionHeadersPinToVisibleBounds = true
layout.estimatedItemSize = CGSize(width: collectionView?.bounds.size.width ?? 0, height: 36) // enables dynamic height
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CustomCell
cell.heightConstraint.constant = CGFloat(indexPath.row * 10 % 100) + 10 // Random constraint to make dynamic height work
return cell
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
return collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath)
}
class CustomCell : UICollectionViewCell {
let identifier = "CustomCell"
#IBOutlet weak var rectangle: UIView!
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
override func awakeFromNib() {
translatesAutoresizingMaskIntoConstraints = false
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
layoutAttributes.bounds.size.height = systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
return layoutAttributes
}
Details of lagging in video: https://vimeo.com/203284395
Update from WWDC 2017:
My colleague was on WWDC 2017, and he asked one of the UIKit engineers about this issue. The engineer confirmed that this issue is known bug by Apple and there is no fix at that moment.
Use the UICollectionViewDelegateFlowLayout method.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
//Calculate your Dynamic Size Here for Each Cell at SpecificIndexPath.
//For Example You want your Cell Height to be dynamic with Respect to indexPath.row number
let cellWidth = collectionView?.bounds.size.width
//Now Simply return these CellWidth and Height, and you are Good to go.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCellIdentifier", for: indexPath)
//You Can Pass TextField As Well as i have considered UITextView for now.
let cellSize = self. calculateSize(cellTextView: cell.textView, cellText: yourArrayOfText[indexPath.row], withFixedWidth: cellWidth)
return cellSize
}
And do not change the Cell Height by changing the Constraint.constant directly. Instead of this simply use Above Delegate method to change height. Changing Cell Constraint can cause issues like this.
Use bellow method to get your desired Size.
func calculateSize(cellTextView: UITextView, cellText: String, withFixedWidth: CGFloat) -> CGSize {
let textView = UITextView()
textView.text = cellText
textView.frame = cellTextView.frame
textView.font = cellTextView.font
textView.tag = cellTextView.tag
let fixedWidth = withFixedWidth
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
return newSize
}
Use Bellow method to calculate the image size.
//Method to Calculate ImageSize.
func calculateImageSize(image: UIImage) -> CGSize {
var newSize = image.size
if newSize.width > (ChatView.maxWidth - 70) {
newSize.width = ChatView.maxWidth - 70
newSize.height = ChatView.maxWidth - 70
}
if newSize.height < 60 {
newSize.height = 60
newSize.width = 60
}
return newSize
}