How to create and use UIcollectionView programmatically? - ios

I have searched a lot for creating a UICollectionView programatically but none of them suggest the simplest way to use it, how to add a label or an image to the UICollectionViewCell. Most of the sites suggest that implementation of UICollectionView is same as UITableView, but the major difference comes when we try to add any image. In UITableView we can allocate the imageViews in cellForRow method where cell == nil and assign images where (cell != nil). but here in case of UICollectionView ItemAtIndexPath method, there is no condition (cell == nil) as in UITableView's CellForRow. As a result we can't effectively allocate variables of UImageViews or Labels etc in itemAtIndexPath method. I Want to know whether there is any alternative other than subclassing the UICollectionViewCell and allocating variables in that custom Class? Can any one help, any help is appreciated.

There is not alternative to create or allocate cells in itemAtIndex method. We need to register the customised class to create any views inside the custom class. something like this :
[UICollectionView registerClass:[CustomCollectionViewClass class] forCellWithReuseIdentifier:#"cellIdentifier"];
here is the best link which I found useful. Hope it helps others

swift :
override func viewDidLoad() {
super.viewDidLoad()
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: 70, height: 70)
let demoCollectionView:UICollectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
demoCollectionView.dataSource = self
demoCollectionView.delegate = self
demoCollectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
demoCollectionView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(demoCollectionView)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 27
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath)
cell.backgroundColor = UIColor.lightGrayColor()
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath)
{
print("User tapped on item \(indexPath.row)")
}

Related

How to use WKWebView inside UICollectionViewCell?

I want to load HTML String in UICollectionview. Each collectionviewcell has WKWebview. When we try load more uicollectionviewcell apps get crashed without any error displayed. I further looking in memory tab and in memory page other process memory get growing every wkwebview loadhtmlstring is fired. How to remove WKWebview memory in swift4... Or other than WKWebview is there any control to show and edit HTML content in iOS? Please help me on this. Thanks in advance..
Note:
I already tried this method suggested in stack overflow
WKWebView causes my view controller to leak
Please give any other suggestion on this.
You can cache the response of the HTML page and after the cell gets disappear or not in view you can remove those cells from view and again when you view that cell you can pick cell info from cache, so that you can save memory.d
I got the same work for do. you can use this below mentioned code.
First you just need to "import WebKit" framework.
class SpecificLessonDetailCVC: UICollectionViewCell {
//MARK:- IBOUTLET & PROPERTIES
#IBOutlet weak var containerView: UIView!
let webView = WKWebView()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collVwLesson.dequeueReusableCell(withReuseIdentifier: "SpecificLessonDetailCVC", for: indexPath) as? SpecificLessonDetailCVC else { return UICollectionViewCell()}
cell.webView.scrollView.showsHorizontalScrollIndicator = false
cell.webView.scrollView.showsVerticalScrollIndicator = false
cell.webView.scrollView.indicatorStyle = .white
cell.webView.scrollView.delegate = self
cell.webView.navigationDelegate = self
cell.webView.loadHTMLStringWithMagic(content: objLessonDetailsVM.responseModel?.data.data[indexPath.row].dataDescription ?? "", baseURL: nil)
cell.webView.frame = CGRect(x: 0, y: 0, width: cell.vwLesson.frame.width, height: cell.vwLesson.frame.height)
cell.vwLesson.addSubview(cell.webView)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if let cell = cell as? SpecificLessonDetailCVC {
cell.webView.becomeFirstResponder()
}
}

Add UICollectionView in UICollectionViewCell

I am using Swift to build an iOS application for the Hospital I work at.
Somehow, in a specific feature I have to put a UICollectionView inside the UICollectionViewCell. The one I want to achieve was for every content of the parent UICollectionView (vertical scrolling) would have several children (Which can be scrolled horizontal) depending on the parent row.
For illustration, I have to display list of doctors (name & photo) and then I have to display each of the practice schedule of them below their name and photo. The practice schedule would vary depending on each doctor. So, I have to put it inside the UICollectionView.
I have tried several solutions that I found on the web, but I still cannot approach it.
The most problem that I can't solve was: I don't know where is the place in the code to load the child data source (doctor schedule) and when I could load it, because I can't have two functions like below:
collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
this is the one I want to achieve
the UIImage and doctor name (UILabel) was in the parent UICollectionViewCell (scroll vertically), and then everything in the box (practice day n practice time) are the child UICollectionView (scroll horizontally)
PS: there are many doctors, and each of the doctor has several practice day.
please help me how to do this
If you really want to insert an collectionView inside a collectionViewCell then there is a pretty simple step. Create an instance of UICollectionView and add it the collectionViewCell. You can use this example if you like.
//
// ViewController.swift
// StackOverFlowAnswer
//
// Created by BIKRAM BHANDARI on 18/6/17.
// Copyright © 2017 BIKRAM BHANDARI. All rights reserved.
//
import UIKit
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"; //Unique cell id
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red; //just to test
collectionView.register(Cell.self, forCellWithReuseIdentifier: cellId) //register collection view cell class
setupViews(); //setup all views
}
func setupViews() {
view.addSubview(collectionView); // add collection view to view controller
collectionView.delegate = self; // set delegate
collectionView.dataSource = self; //set data source
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true; //set the location of collection view
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true; // top anchor of collection view
collectionView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true; // height
collectionView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true; // width
}
let collectionView: UICollectionView = { // collection view to be added to view controller
let cv = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()); //zero size with flow layout
cv.translatesAutoresizingMaskIntoConstraints = false; //set it to false so that we can suppy constraints
cv.backgroundColor = .yellow; // test
return cv;
}();
//deque cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
// cell.backgroundColor = .blue;
return cell;
}
// number of rows
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5;
}
//size of each CollecionViewCell
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: 200);
}
}
// first UICollectionViewCell
class Cell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let cellId = "CellId"; // same as above unique id
override init(frame: CGRect) {
super.init(frame: frame);
setupViews();
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId); //register custom UICollectionViewCell class.
// Here I am not using any custom class
}
func setupViews(){
addSubview(collectionView);
collectionView.delegate = self;
collectionView.dataSource = self;
collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true;
collectionView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true;
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true;
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true;
}
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout();
layout.scrollDirection = .horizontal; //set scroll direction to horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout);
cv.backgroundColor = .blue; //testing
cv.translatesAutoresizingMaskIntoConstraints = false;
return cv;
}();
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath);
cell.backgroundColor = .red;
return cell;
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5;
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.frame.width, height: self.frame.height - 10);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This might be a little late, but for people out here still trying to find an answer.
After some research and digging, I stumbled upon several posts stating reasons why you should NOT have your cell be the delegate for you collectionView. So, I was lost because pretty much all solutions I had found were doing this, until I finally found what I believe is the best way to have nested collectionViews.
To give some background, my app included not only one but 2 collectionViews inside different cells of another collectionView, so setting the delegates with tags and all that, wasn't really the best approach nor the correct OO solution.
So the best way to do it is the following:
First you have to created a different class to serve as your delegate for the inner collectionView. I did it as such:
class InnerCollectionViewDelegate: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// CollectionView and layout delegate methods here
// sizeForItemAt, cellForItemAt, etc...
}
Now, in your inner collectionView (or rather the cell where you have the inner collectionView) create a function that will allow you to set its delegates
class InnerCell: UICollectionViewCell {
var collectionView: UICollectionView
init() {
let layout = UICollectionViewFlowLayout()
collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height), collectionViewLayout: layout)
}
func setCollectionViewDataSourceDelegate(dataSourceDelegate: UICollectionViewDataSource & UICollectionViewDelegate) {
collectionView.delegate = dataSourceDelegate
collectionView.dataSource = dataSourceDelegate
collectionView.reloadData()
}
}
And lastly, in your ViewController where you have your outermost (main) collectionView do the following:
First instantiate the delegate for the inner collectionView
var innerDelegate = InnerCollectionViewDelegate()
and then
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if let cell = cell as? InnerCell {
cell.setCollectionViewDataSourceDelegate(dataSourceDelegate: innerDelegate)
}
}
This might not be perfect, but at least you have separation of concerns, as your cell should NOT be the delegate. Remember your cell should only be responsible for displaying info, not trying to figure out what the size of the collectionView should be, etc.
I did find similar answers that dealt with setting the collectionViews tag and whatnot, but I found that that made it way harder to deal with each collectionView individually, plus dealing with tags can't result in spaghetti code or unintended behaviours.
I left out registering and dequeuing the cell, but I'm sure you're all familiar with that. If not, just let me know and I'll try to walk you through it.
There are multiple ways to tackle the problem of a horizontal collection inside another a vertical list collection.
The simplest would be to make the ViewController you are presenting the main UICollectionView to the dataSouce and delegate for both collection views. You can set the collection view inside the cell also to be served from here.
This article about placing collection view inside a table view explains the problem in a much elaborate way and the code for the same can be found here.
Add collectionView in collection view cell , and add delagate methods in collectionviewclass.swift. Then pass list you want to show in cell in collectionview's cellforrowatindexpath. If you didn't success on implimenting it then let me know . i will provide you code as i have already implemented it in that way.

Registering multiple cells in UICollectionView (swift 3)

For UICollectionViews, is it possible to have multiple cell types?
For example:
media_cell
regular_cell
ad_cell
Would you just have to register them or do you have to include an if statement and change the layout according to some variable to achieve this effect.
For example:
if cell.ad == true {
}
The reason being, I want a slightly different sized cell for when an image is present. I guess resizing the cell could work but I haven't seen anything on this online.
Any suggestions
Try this:
1. Register two(or more) cells.
2.Configure cellforItem for each.
3. Configure sizeForItem for each.
First:
self.collectionView.register(SmallCell.self, forCellWithReuseIdentifier: "smallCell")
self.collectionView.register(BigCell.self, forCellWithReuseIdentifier: "bigCell")
And then:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if dataSource[indexPath.item].hasImage {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: “smallCell”, for: indexPath) as! SmallCell
let model = dataSource[indexPath.item]
cell.model = model
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: “bigCell”, for: indexPath) as! BigCell
let model = dataSource[indexPath.item]
cell.model = model
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if dataSource[indexPath.item].hasImage {
return CGSize(width: collectionView.frame.width, height: cellHeight+100)
} else {
return CGSize(width: collectionView.frame.width, height: cellHeight)
}
}
Two things and everything should be works:
You can register any number of cells in one collection view.
Check out self-sizing cells topic and you should not worry about different sizes of cells.
Great tutorial
More info in this brilliant answer here
Good luck :)

Datasource methods not being called properly when implemented in custom UICollectionView class

Scenario - I have to create a custom UICollectionView class programmatically which has to be presented in any place I want.
Code till now -
For custom UICollectionView
class ABSegmentView: UICollectionView,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource {
var segmentProperties=segmentControlProperties()//segmentControlProperties is a modal class having relevant details regarding about population of collection view.
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.dataSource = self
self.delegate = self
self.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cellIdentifier")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int{
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print(segmentProperties.titleArray)
return segmentProperties.titleArray.count//data properly being received over here
}
//not getting called
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = self.dequeueReusableCellWithReuseIdentifier("cellIdentifier", forIndexPath: indexPath)
cell.backgroundColor = UIColor.redColor()
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{
return CGSizeMake(self.segmentProperties.segmentHeight, self.segmentProperties.segmentWidth)
}
}
Code for adding this collection view in some place -
let segment = ABSegmentView(frame: CGRectMake(0, 0, 200, 200), collectionViewLayout: UICollectionViewLayout())
segment.segmentProperties.segmentWidth = 60
segment.segmentProperties.segmentHeight = 50
segment.segmentProperties.titleArray = ["heyy","heyy","heyy","heyy","heyy","heyy"]
self.view.addSubview(segment)
So what is getting added is only an empty collection view.
Reason Figured out -
On debugging I found that my data source method cellForItemAtIndexPath() & func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) are not getting called.
Question - I am not sure what I am doing for my required scenario is the right implementation or not. Please amend me if I am missing something somewhere or what might be my mistakes.
Edit :
Answer -
Thanks to Santosh's answer. I figured out that I misunderstood the concept of collectionViewLayout.
Findings -
I have to set a proper flow layout for the collection view as a
proper flow layout with correct spacing and other values are quite
essential for a collection view to be properly laid.
CollectionView Flow layout is what lays the UI of collection view i.e the grid view.
There are many questions in StackOverflow which relates of data source methods not being called due to improper laying of collectionViewFlowLayout.
References that worked out for me apart from accepted answer -
https://stackoverflow.com/a/14681999/5395919
Other instances when some one can encounter such problems -
-When we set our cell size quite bigger than our collection view.
-When our cell layout size is too big or isn't appropriately held by the collection view.
You are using UICollectionViewLayout to instantiate your custom collectionView with layout. Try using UICollectionViewFlowLayout. Below code may help you, let me know if it works:
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 20//you can change this value
layout.scrollDirection = .Vertical//or may be .Horizontal
let segment = ABSegmentView(frame: CGRectMake(0, 0, 200, 200), collectionViewLayout: layout)
segment.segmentProperties.segmentWidth = 60
segment.segmentProperties.segmentHeight = 50
segment.segmentProperties.titleArray = ["heyy","heyy","heyy","heyy","heyy","heyy"]
self.view.addSubview(segment)
segment.reloadData()
In order to get datasource updated you need to call reloadData()

Self Sizing CollectionViewCell

I try to make my collectionView Cell height as per content.
I tried with Self Sizing CollectionViewCell but it is not working.
I used auto layout in CollectionView Cell.
Cell have three Labels with top,left,bottom,right Constraints & have Vertical Compression Ration is Required(1000).
My code is as follow:
override func viewDidLoad() {
super.viewDidLoad()
let flowlayout = col.collectionViewLayout as! UICollectionViewFlowLayout
flowlayout.estimatedItemSize = CGSizeMake((self.view.frame.size.width - 60) / 2 , 1)
}
}
//MARK: CollectionView
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 17
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = col.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CustomCell
if indexPath.row == 0 {
cell.lbl1.text = "Lorem ispum Simple text to show if it working or not"
}
else{
cell.lbl1.text = "\(indexPath.row)"
}
return cell
}
Some People are saying that it is not possible for collectionView Self Sizing Cell, even though Apple claims it.
You have to calculate manually.
If I try to calculate it manually then I am also unable to get cell object in sizeForItemIndexPath

Resources