Insert ViewControllers in UICollectionView calling lifeCycle methods - Swift - Programmatically - ios

I'm using a UICollectionView to create a Swipeable Menu like this:
I've chosen to use a collectionView because it's so much easier than a PageViewController to perform some tasks (since UICollectionView is a subclass of UIScrollView).
There's one downside though, I don't know how to insert the ViewContollers inside the UICollectionViewcells and if upon scrolling the viewController's methods (such as ViewDidAppear, ViewDidDisappear) are called, I actually tried so many things but I couldn't find a way to solve this.
So summarising what I want to accomplish is: Insert ViewControllers inside CollectionViewCells so that when I scroll through the CollectionView, the lifecycle methods of VCs (ViewDidAppear, ViewDidDisappear) are called.
On StackOverFlow I've seen many topics talking about how to insert ViewControllers in cells but NEVER about calling the lifecycle methods inside them upon scrolling.
This is my CollectionView code:
private let reuseIdentifier1 = "Cell1"
private let reuseIdentifier2 = "Cell2"
class CollectionViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
self.collectionView.isPagingEnabled = true
self.collectionView.bounces = false
self.collectionView!.register(CollectionViewCell1.self, forCellWithReuseIdentifier: reuseIdentifier1)
self.collectionView.register(CollectionViewCell2.self, forCellWithReuseIdentifier: reuseIdentifier2)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.item {
case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier1, for: indexPath)
return cell
default:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier2, for: indexPath)
return cell
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return .init(width: view.frame.width, height: view.frame.height)
}}

Related

CollectionView method cellForItemAt indexPath: not being called (Swift)

I have used UICollectionViewController and I'm trying to add images to my collection view cell but UICollectionView method is not being called.
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! CircularCollectionViewCell
cell.imageName = images[indexPath.row]
return cell
}
Hint: CellforRow at:indexPath method won't be called if the size or position of the collectionView is changed before reload, make sure you're not doing that.
The cellForItemAtIndexPath will not get called if you do not provide the content size information to the collection view.
What you can do is set up the collectionViewLayout as shown below:
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
collectionView.collectionViewLayout = flowLayout
If your UICollectionView have not proper content size, then cellForItemAt will not called.
I have two solution for it.
Call collectionView.layoutIfneeded() before reloading collectionView.
Add collectionViewLayout in main thread before reloading collectionView which helps to adjust content size.
DispatchQueue.main.async {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .horizontal
self.collectionView.collectionViewLayout = flowLayout
}
self.collectionView.reloadData()
self.collectionView.reloadData()
In UICollectionViewController subclasses delegate and dataSource are set by default.
And 0 is used in datasource methods by default. You need to change these values.
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(images.count)
return images.count
}

sizeForItemAt not called while scrolling

I am having UICollectionView inside a UITableViewCell and its delegate and datasource are declared there in table cell class. It works fine for first time loading but later on changes it's size when I scroll collection.
I noticed sizeForItemAt is called only while loading but not while setting the cells during scroll.
class HomeCell: UITableViewCell {
#IBOutlet weak var collHomeSongs: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func setCollection() {
collHomeSongs.delegate = self
collHomeSongs.dataSource = self
}
}
extension HomeCell: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width: 120, height: 180)
}
}
I just got it's solution this problem arises in Xcode 11+, and solution to it is set collection view automatic size to "None".
Refer: Why on Xcode 11, UICollectionViewCell changes size as soon as you scroll (I already set size in sizeForItem AtIndexPath:)?

Modifying the size of the cell of UICollectionView

I am trying to modify the size of the cells that the UICollectionview contains. I believe that the sizeForItemAtIndexPath should do the trick. Yet nothing is happening.
I have looked into similar questions, they have advised doing the same thing. I am sort of suspicious of my inheritance.
The problem is that the cells remain in the same size regardless of the value they were fed in.
class HikeViewController: UIViewController {
var test: Int?
#IBOutlet weak var options: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.prefersLargeTitles = false
print(test!)
options.dataSource = self
options.delegate = self
}
}
extension HikeViewController: UICollectionViewDelegate, UICollectionViewDataSource {
//collectionCell
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.green
return cell
}
func collectionView(collectionView : UICollectionView,layout collectionViewLayout:UICollectionViewLayout,sizeForItemAtIndexPath indexPath:NSIndexPath) -> CGSize
{
return CGSize(width: 400, height: 500)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath.row)
}
}
And when I conform to UICollectionViewDelegateFlowLayoutthe app termainates where I set the followings:
options.dataSource = self
options.delegate = self
And the error is:
Could not cast value of type 'HikingClub.HikeViewController' (0x108e1f948) to 'UICollectionViewDataSource' (0x10e723ff0).
It's worth adding that I have made sure of the connection between HikeViewController and the storyboard.
extension HikeViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
}
It should be something like this
Make your extension a subclass of UICollectionViewDelegateFlowLayout
Use the following function
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// code
}
You need to specify that you implement the protocol UICollectionViewDelegateFlowLayout in your class declaration.
extension HikeViewController: UICollectionViewDelegateFlowLayout {}
also you have are not calling the delegate method, just defining a new one.
check the first parameter (collectionView -> _ collectionView)
func collectionView(_ collectionView : UICollectionView,layout collectionViewLayout:UICollec

Using CollectionView in UIView with xib file

i'm doing with this, i want to use CollectionView, but i haven't seen prototype cell, and don't know how to use CollectionView in this case, can someone help me ?
I try to use like this way but it take alot of time and hard to manage than UICollectionView
The main way to use UICollectionView is by managing the logic programmatically.
First, create a new class which inherits from UICollectionViewCell. Choose if you want to include a xib to easily design your cell:
Design your cell with Interface Builder or programmatically.
Create your main view controller including a xib (or a storyboard) with the collection view inside and link it to the associated class via Interface Builder. Alternatively you can add a collection view programmatically to your UIViewController
Make the target view controller conform to the UICollectionViewDelegate and UICollectionViewDataSource protocols by declaring them after the father class:
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
//...
}
Register the associated nib or the class for your cell in the viewDidLoad method and associate the datasource and delegate protocols to the view controller class:
let cellIdentifier = "cellIdentifier"
override func viewDidLoad() {
super.viewDidLoad()
//if you use xibs:
self.collectionView.register(UINib(nibName:"MyCollectionCell", bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
//or if you use class:
self.collectionView.register(MyCollectionCell.self, forCellWithReuseIdentifier: cellIdentifier)
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
Implement the methods declared in the UICollectionViewDelegate and UICollectionViewDataSource protocols :
let objects = ["Cat", "Dog", "Fish"]
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.objects.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! MyCollectionCell
//in this example I added a label named "title" into the MyCollectionCell class
cell.title.text = self.objects[indexPath.item]
return cell
}
Run your app in the simulator (or on a real device) and.. Et voilĂ ! :)
For more info: https://developer.apple.com/reference/uikit/uicollectionview
ok first you must have the IBOutlet of your collection view and implements the methods like this
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource,UICollectionViewDelegateFlowLayout{
#IBOutlet var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
count = 9;
let nib = UINib(nibName: "yourItemView", bundle: nil)
collectionView.registerNib(nib, forCellWithReuseIdentifier: "yourItemView")
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
ok in the function you add a xib file, next you must create that extend from UICollectionViewCell, and when you finish this you must override the next methods
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return count
// the numbers of items
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {//size of your item for screen sizes
let wsize = UIScreen.mainScreen().bounds.size.width
switch(wsize){
case 414:
return CGSize(width: 190, height: 102)
case 375:
return CGSize(width: 190, height: 102)
case 320:
return CGSize(width: 174, height: 102)
default:
return CGSize(width: 174, height: 102)
}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("yourItemView", forIndexPath: indexPath) as! yourItemView
return cell
}
and this is all, good luck

Collectionview with multiple different collection viewcell

I'm building a survey app and I want to use such view
in this example there's only two answer possible but I want to be possible to have like 3 / 4 answer and different kind of survey,
I will receive a JSON to build it with the type of the question and the answer possible etc ..
My main problem is that I don't really know how to proceed, I'm a newbie on iOS development atm and I don't want to try too many thing and have spaghetti code so if someone have an idea on how to do this collection view with different view depending on the json
Thanks o/
There is lot of ways to achieve it but as in your case you want with Collectionview with multiple different collection viewcell
In ViewController.swift, conform few UICollectionView protocol.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate , UICollectionViewDelegateFlowLayout{
#IBOutlet weak var mainCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
mainCollectionView.isScrollEnabled = false
}
then implement collection view data source and delegate methods.
// MARK: - UICollectionViewDataSource protocol
// tell the collection view how many cells to make
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
// make a cell for each cell index path
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print("indexPath.row \(indexPath.row)")
if indexPath.row == 0{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "firstCell", for: indexPath)
if let button = cell.contentView.viewWithTag(2) as? UIButton{
button.addTarget(self, action: #selector(MoveToNextCell), for: .touchUpInside)
}
return cell
}
else if indexPath.row == 1{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "secondCell", for: indexPath)
if let button = cell.contentView.viewWithTag(3) as? UIButton{
button.addTarget(self, action: #selector(MoveToNextCell), for: .touchUpInside)
}
return cell
}
else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "thirdCell", for: indexPath)
if let button = cell.contentView.viewWithTag(4) as? UIButton{
button.addTarget(self, action: #selector(MoveToNextCell), for: .touchUpInside)
}
return cell
}
}
// MARK: - UICollectionViewDelegate protocol
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// handle tap events
print("You selected cell #\(indexPath.item)!")
}
then implement UICollectionViewDelegateFlowLayout methods.
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.view.frame.width , height: self.view.frame.height)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
func collectionView(_ collectionView: UICollectionView, layout
collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1.0
}
Then finally for moving between cells
func MoveToNextCell(){
let collectionBounds = self.mainCollectionView.bounds
let contentOffset = CGFloat(floor(self.mainCollectionView.contentOffset.x + collectionBounds.size.width))
self.moveToCell(contentOffset: contentOffset)
}
func moveToPreviousCell(){
let collectionBounds = self.mainCollectionView.bounds
let contentOffset = CGFloat(floor(self.mainCollectionView.contentOffset.x - collectionBounds.size.width))
self.moveToCell(contentOffset: contentOffset)
}
func moveToCell(contentOffset: CGFloat){
let frame: CGRect = CGRect(x : contentOffset ,y : self.mainCollectionView.contentOffset.y ,width : self.mainCollectionView.frame.width,height : self.mainCollectionView.frame.height)
self.mainCollectionView.scrollRectToVisible(frame, animated: true)
}
That's pretty much all you need to achieve that.

Resources