I'm working on a requirement where I need to add the items in a UICollectionView dynamically.
Here is my code of ViewController
import UIKit
class ViewController: UIViewController {
enum Direction {
case Horizonatal
case Verticle
}
var enumDirection: Direction = .Verticle
var direction = "Verticle"
var SectionsAndRows = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
SectionsAndRows.append(4)
SectionsAndRows.append(3)
SectionsAndRows.append(2)
SectionsAndRows.append(1)
//SectionsAndRows.append(5)
}
#IBOutlet var gridCollectionView: UICollectionView! {
didSet {
gridCollectionView.bounces = false
}
}
#IBOutlet var gridLayout: UICollectionViewFlowLayout! {
didSet {
//gridLayout.stickyRowsCount = 0
gridLayout.scrollDirection = .horizontal
//gridLayout.stickyColumnsCount = 0
gridLayout.minimumLineSpacing = 5
gridLayout.minimumInteritemSpacing = 5
}
}
}
// MARK: - Collection view data source and delegate methods
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return SectionsAndRows.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(SectionsAndRows[section])
return SectionsAndRows[section]
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CollectionViewCell.reuseID, for: indexPath) as? CollectionViewCell else {
return UICollectionViewCell()
}
print("Current Section ==\(indexPath.section) CurrentRow ===\(indexPath.row) and its rows count ==\(SectionsAndRows[indexPath.section])")
cell.titleLabel.text = ""
cell.btn.addTarget(self, action: #selector(handleAdd(sender:)), for: .touchUpInside)
cell.btn.tag = (indexPath.section * 1000) + indexPath.row
if enumDirection == .Verticle {
if indexPath.section == SectionsAndRows.count - 1 {
cell.btn.setTitle("+", for: .normal)
} else {
cell.btn.setTitle("\(indexPath)", for: .normal)
}
}
return cell
}
#objc func handleAdd(sender: UIButton) {
// Perform some opration
}
}
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 5
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 100, height: 100)
}
}
CollectionViewCell.swift
import UIKit
class CollectionViewCell: UICollectionViewCell {
static let reuseID = "CollectionViewCell"
#IBOutlet weak var btn: UIButton!
#IBOutlet weak var titleLabel: UILabel!
}
If you run the code, it will show a Collection of 1 row and 1 column in each row. If you uncomment the last line of viewDidLoad() (SectionsAndRows.append(5)) function, then it works fine.
My observation is that the last section of the CollectionView will have the highest number of a column. Is that correct or is this a bug of a CollectionView?
Related
How to make UICollectionView dynamic height? The height of the UICollectionView should depend on the number of cells in it.
class ProduitViewController: UIViewController {
var productCollectionViewManager: ProductCollectionViewManager?
var sizeCollectionViewManager: SizeCollectionViewManager?
var product: ProductModel?
var selectedSize: String?
#IBOutlet weak var productCollectionView: UICollectionView!
#IBOutlet weak var sizeCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
}
private extension ProduitViewController {
func setup() {
guard let product = product else { return }
colorNameLabel.text = product.color[0].name
sizeCollectionViewManager = SizeCollectionViewManager.init()
sizeCollectionView.delegate = sizeCollectionViewManager
sizeCollectionView.dataSource = sizeCollectionViewManager
sizeCollectionViewManager?.set(product: product)
sizeCollectionViewManager?.didSelect = { selectedSize in
self.selectedSize = selectedSize
}
sizeCollectionView.reloadData()
}
}
Collection View Manager
import UIKit
final class SizeCollectionViewManager: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
var sizeProduct: [SizeModel] = []
var didSelect: ((String) -> Void)?
func set(product: ProductModel) {
sizeProduct = product.size
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return sizeProduct.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SizeCell", for: indexPath) as? SizeCollectionViewCell {
cell.configureCell(cellModel: sizeProduct[indexPath.row])
return cell
}
return UICollectionViewCell.init()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width / 3 + 20
let height: CGFloat = 35
return CGSize(width: width, height: height)
}
}
The height is now 35. If it is not set static, then the collection view will disappear altogether from the screen.
Screenshot Storyboard
You should set a height constraint on the UICollectionView reference. Once the constraint is set, you can calculate and set the constraint value based on number of objects, since you know how many rows it should display.
I want to have this progression view (pod) inside each collectionviewcell. When I run the app, the progression is 0%, it should animate to 100% for testing, but it stays at 0%. Only when I swipe up and down (refresh the cells???) it starts animating to 100%. This is my code:
import UIKit
import MKRingProgressView
class myCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var ringView: UIView!
let ringsize = 150
var ringProgressView = RingProgressView()
override func layoutSubviews() {
ringProgressView = RingProgressView(frame: CGRect(x: 0, y: 0, width: ringsize, height: ringsize))
ringProgressView.startColor = .red
ringProgressView.endColor = .magenta
ringProgressView.ringWidth = CGFloat(ringsize) * 0.15
ringProgressView.progress = 0.0
ringProgressView.shadowOpacity = 0.5
ringView.addSubview(ringProgressView)
}
}
extension myCollectionViewCell{
func setProgress(progress: Double){
UIView.animate(withDuration: 0.5){
self.ringProgressView.progress = progress
}
}
}
import UIKit
private let reuseIdentifier = "Cell"
class myCollectionViewController: UICollectionViewController {
#IBOutlet var myCollectionView: UICollectionView!
var cellColor = true
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 7
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! myCollectionViewCell
cell.setProgress(progress: 1.0)
return cell
}
}
Can someone help me? Thanks!!
Call myCollectionView.reloadData() inside viewDidAppear method.
My problem is when I click collection view it shows the data but if I click others collection view it show error:
Thread 1: Fatal error: Index out of range.
How to fix that? Thank you
Here my code
import UIKit
struct item {
var name : String
var price : String
var image : String
}
class SearchViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var items = [item]()
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionviewflow: UICollectionViewFlowLayout!
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
let cellIndex = indexPath.item
cell.imageView.image = UIImage(named: items[indexPath.item].image)
cell.labelname.text = items[indexPath.item].name
cell.labelprice.text = items[indexPath.item].price
cell.contentView.layer.cornerRadius = 10
cell.contentView.layer.borderWidth = 1.0
cell.contentView.layer.borderColor = UIColor.gray.cgColor
cell.backgroundColor = UIColor.white
cell.layer.shadowColor = UIColor.gray.cgColor
cell.layer.shadowOffset = CGSize(width: 0.0, height: 2.0)
cell.layer.shadowRadius = 2.0
cell.layer.shadowOpacity = 1.0
cell.layer.masksToBounds = false
cell.layer.shadowPath = UIBezierPath(roundedRect: cell.bounds, cornerRadius: cell.contentView.layer.cornerRadius).cgPath
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard!.instantiateViewController(identifier: "ItemDetailViewController") as! ItemDetailViewController
vc.name = items[indexPath.item].name
vc.price = items[indexPath.item].price
vc.imagee = items[indexPath.item].image
self.navigationController?.pushViewController(vc, animated: true) }
override func viewWillDisappear(_ animated: Bool) {
items.removeAll()
}
}
extension SearchViewController : UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
}
and here my itemdetailsviewcontroller
import UIKit
class ItemDetailViewController: UIViewController {
var name : String = ""
var price : String = ""
var imagee : String = ""
#IBOutlet weak var labelname: UILabel!
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var labelprice: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
labelname.text = name
labelprice.text = price
image.image = UIImage(named: imagee)
}
}
Remove items.removeAll() from the viewWillDisappear(_ animated: Bool). You are changing the source data of the collectionView without it being aware. When you tap a cell a second time, the collectionView is trying to use an item from items but you emptied it when you pushed the controller on the first cell selection.
I have created a carousel view using UPCarouselFlowLayout and it works well. But when the UIButton in the view is clicked, no action occurs. How do I make it so that when the UIButton is clicked, it calls a particular action?
CollectionViewCell:
class MagicCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var magicview: UIView!
#IBOutlet weak var magicimage: UIImageView!
#IBOutlet weak var magiclabel: UILabel!
#IBOutlet weak var magicbutton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
self.magicview.isUserInteractionEnabled = true
// Initialization code
DispatchQueue.main.async {
self.magicview.layer.shadowColor = UIColor.gray.cgColor
self.magicview.layer.shadowOpacity = 0.5
self.magicview.layer.shadowOpacity = 10.0
self.magicview.layer.shadowOffset = .zero
self.magicview.layer.shadowPath = UIBezierPath(rect: self.magicview.bounds).cgPath
self.magicview.layer.shouldRasterize = false
}
}
}
Magic1.swift
import UPCarouselFlowLayout
class Magic1: UIViewController,UICollectionViewDataSource,UICollectionViewDelegate {
#IBOutlet weak var magiccollection: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
magiccollection.register(UINib.init(nibName: "MagicCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "magiccidentifier")
let floawLayout = UPCarouselFlowLayout()
floawLayout.itemSize = CGSize(width: UIScreen.main.bounds.width - 60.0, height: magiccollection.frame.size.height)
floawLayout.scrollDirection = .horizontal
floawLayout.sideItemScale = 0.8
floawLayout.sideItemAlpha = 1.0
floawLayout.spacingMode = .fixed(spacing: 5.0)
magiccollection.collectionViewLayout = floawLayout
magiccollection.delegate = self
magiccollection.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = sexeducollec.dequeueReusableCell(withReuseIdentifier: "magiccidentifier", for: indexPath) as!MagicCollectionViewCell
if indexPath.row == 0{
cell.magiclabel.text = "Tester title"
cell.sexedimage.image = #imageLiteral(resourceName: "merlin")
}else if indexPath.row == 1{
cell.magiclabel.text = "love is in the air"
}else{
cell.magiclabel.text = "Title - \(indexPath.row + 1)"
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 0{
print("Accio")
}else if indexPath.row == 1{
print("alohamora")
}
}
}
What I'm trying to do is when magicbutton is clicked at indexPath.row == 0 it calls a specific action and when it is clicked at indexPath.row == 1 it calls a specific function.
Try this approach! Also, You can call closure at IBAction
class ViewController: UICollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "magiccidentifier", for: indexPath) as? MagicCollectionViewCell {
switch indexPath.row {
case 0:
cell.onCellTouched = { print("Hi")}
default:
cell.onCellTouched = { print("By") }
}
return cell
}
return UICollectionViewCell()
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let item = collectionView.cellForItem(at: indexPath) as? MagicCollectionViewCell
item?.onCellTouched()
}
}
class MagicCollectionViewCell: UICollectionViewCell {
var onCellTouched: () -> Void = { assertionFailure("Cell action didn't set up") }
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = .black
}
}
You are missing an IBAction of your button in MagicCollectionViewCell. So add an action for your button:
#IBAction func buttonAction(_ sender: UIButton) {
}
in my view controller I Am loading a custom CollectionViewCell with subclass. Based on the position of a cell's indexpath I want to format the text labels differently. I.e. first row has only one cell with bigger text, whereas the second has two cell with smaller text.
How can I access the indexpath from my UICollectionView in my UICollectionViewCell subclass? I tried a delegate protocol but this always returns nil.
Code below and Thanks so much!
Markus
UICollectionViewController:
import UIKit
protocol WorkoutDataViewControllerCVDataSource: AnyObject {
func workoutType(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> WorkoutType
func workoutDistance(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
func workoutDuration(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
func workoutInstantVelocity(for workoutDataViewControllerCV: WorkoutDataViewControllerCV) -> Double
}
final class WorkoutDataViewControllerCV: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
weak var dataSource: WorkoutDataViewControllerCVDataSource!
private lazy var velocityFormatter = VelocityFormatter(dataSource: self, delegate: self)
private lazy var averageVelocityFormatter = VelocityFormatter(dataSource: self, delegate: self)
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.register(MeasurementCollectionViewCell.preferredNib, forCellWithReuseIdentifier: MeasurementCollectionViewCell.preferredReuseIdentifier)
}
}
// MARK: - Managing UICollectionView
extension WorkoutDataViewControllerCV: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Measurement Cell", for: indexPath)
return cell
}
}
extension WorkoutDataViewControllerCV: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
let availableWidth = self.view.frame.width
switch indexPath.row {
case 0: return CGSize(width: availableWidth, height: 150)
case 1: return CGSize(width: availableWidth/2.1, height: 150)
case 2: return CGSize(width: availableWidth/2.1, height: 150)
case 3: return CGSize(width: availableWidth, height: 150)
default:
return CGSize(width: availableWidth/2.1, height: 150)
}
}
}
// MARK: - Managing VelocityFormatter
extension WorkoutDataViewControllerCV: VelocityFormatterDataSource {
func duration(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutDuration(for: self)
}
func distance(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutDistance(for: self)
}
func instantVelocity(for velocityFormatter: VelocityFormatter) -> Double {
return dataSource.workoutInstantVelocity(for: self)
}
}
UICollectionViewCell.swift
import UIKit
final class MeasurementCollectionViewCell: UICollectionViewCell {
#IBOutlet private var measurementPropertyLabel: UILabel!
#IBOutlet private var measurementValueLabel: UILabel!
#IBOutlet private var measurementUnitLabel: UILabel!
static let preferredReuseIdentifier = "Measurement Cell"
static let preferredNib = UINib(nibName: "MeasurementCollectionViewCell", bundle: nil)
override func awakeFromNib() {
super.awakeFromNib()
updateMeasurement(property: "Speed", value: "100", unit: "km/h")
//measurementValueLabel.font = measurementValueLabel.font.monospacedDigitFont
}
func updateMeasurement(property: String, value: String, unit: String?) {
measurementPropertyLabel.text = property
measurementValueLabel.text = value
measurementUnitLabel.text = unit
}
}
Get the instance of cell in UICollectionView delegate method collectionView(_, didSelectItemAt _).
extension WorkoutDataViewControllerCV: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? MeasurementCollectionViewCell {
cell.selectedIndexPath(indexPath)
}
}
}
The indexPath will be passed as an argument in method selectedIndexPath to MeasurementCollectionViewCell from above method.
class MeasurementCollectionViewCell: UICollectionViewCell {
......
func selectedIndexPath(_ indexPath: IndexPath) {
//Do your business here.
}
}
You can use the responder chain to get the collection view of a cell with which you can get the index path. Just add these extensions in a new file called UICollectionViewCell+IndexPath.swift.
extension UIResponder {
func next<T: UIResponder>(_ type: T.Type) -> T? {
return next as? T ?? next?.next(type)
}
}
extension UICollectionViewCell {
var indexPath: IndexPath? {
return next(UICollectionView.self)?.indexPath(for: self)
}
}
Now inside your cell, you can use self.indexPath
Pretty straight forward way would be storing the indexPath into the subclass of UICollectionViewCell class. Assign it while returning from cellForRow at: index path. So now the subclassed collectionviewcell has access to the indexpath of it's own