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()
Related
Im trying to get rounded UICollection cell but i found out that it's not working somehow and I can't come up why. I have a TableView in regular ViewController, in this tableview i've a custom table cell (in specified section) and in this custom table cell I've a CollectionView also with custom collectionCell and if i do anything rounded there, it's just not rounding anything. I cliped testBtn into bounds, but it wasn't work with this... I also tried testBtn.layer.masksToBounds with no success. However, if I'am doing a rounding in parent (tableView cell itself), it's working just good...
CollectionViewCell code:
import UIKit
class ProfileTileCollectionViewCell: UICollectionViewCell {
#IBOutlet var testBtn: UIButton!
#IBOutlet var contView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
testBtn.layer.cornerRadius = 20
testBtn.clipsToBounds = true
// Initialization code
}
#IBAction func testClicked(_ sender: Any) {
print("clicked")
}
}
Screenshot of the mainView: Screenshot.png
Blue color is a background of collectionView, gray is background of cell and violet is a button. I don't know if it's getting late but it's weird for me, not be able to get working this thing... I'm missing something... Any help I will appreciate !
Try to set testBtn.layer.masksToBounds = true
From the screenshot, I guess you have added corner radius in your collection view and set clipsToBounds true. If you don't need rounded collection view then remove cornerRadius and set clipsToBounds false. Or if you need both of them rounded then maintain padding between collection view and button. You can do this by fine-tuning the section inset property.
I had a great 8 hours of sleep and cuz that and Rob's comment, I tried to dig into debug for a bit, and I noticed a warning message which were saying that behaviour of UICollectionViewFlowLayout is not set... (and so on). So I decided to set up UICollectionViewDelegateFlowLayout like this:
extension ProfileTilesTableViewCell: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top:0, left:10, bottom:0, right: 10)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (collectionView.frame.width - 40) / 3 , height: collectionView.frame.height - 5)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 10
}
And finally :D somehow it started working properly !
So I deleted everything that I used for testing, deleted testBtn and changed testBtn.roundedCorners to contView.roundedCorners, set back the collectionView properties and it's looking near to what I'm looking for: Screenshot2.png
Thanx for your answers, especially to Rob's one. This is what I was looking for !
Best regards SB !
I noticed a big issue where in right to left languages, the cells order is not properly reversed, only the alignment is correct. But only for horizontal flow layout, and if the collection view contain different cell sizes! Yes, I know this sound insane. If all the cells are the same size, the ordering and alignment is good!
Here is what I got so far with a sample app (isolated to make sure this is the actual issue, and not something else):
(First bar should always be drawn blue, then size increase with index.)
Is this an internal bug in UICollectionViewFlowLayout (on iOS 11)? Or is there something obvious that I am missing?
Here is my test code (Nothing fancy + XIB with UICollectionView):
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 6
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "test", for: indexPath)
cell.backgroundColor = (indexPath.item == 0) ? UIColor.blue : UIColor.red
return cell
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: (10 * indexPath.item) + 20, height: 170)
}
Automatic right-to-left support on dynamically-sized UICollectionViews is not a supported configuration. For this to work, you need to explicitly sign up for automatic layout mirroring as follows:
Create a subclass of UICollectionViewFlowLayout
Override flipsHorizontallyInOppositeLayoutDirection, and return true in Swift or YES in Objective-C
Set that as the layout of your collection view
This property is defined on UICollectionViewLayout (parent of Flow), so you can technically use this property on any custom layout you already have.
I believe that for this you will have to implement your own custom collectionViewLayout - although I understand that one would expect that it would automatically work just as the right-to-left on the rest of the components.
I've been trying to set up a collection view where I have the user submit several strings which I toss in an array and call back through the collection view's cellForItemAt function. However, whenever I add a row to to the top of the collection view, it adds the cell label literally on top of the last cell label so they stack like this. Notice how every new word I add includes all the other previous words in the rendering.
The code I have at cellForItemAt is
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "InterestsCell", for: indexPath) as? InterestsCell {
cell.interestLabel.text = array[indexPath.row]
return cell
} else {
return UICollectionViewCell()
}
}
and the code I have when the add button is pressed is
func addTapped() {
let interest = interestsField.text
array.insert(interest!, at: 0)
interestsField.text = ""
collectionView.reloadData()
}
I'm not sure what's going on. I looked everywhere and tried to use prepareForReuse() but it didn't seem to work. I later tried deleting cells by calling didSelect and the cells would not disappear again. Any help is appreciated!
This is the code I have in my custom collection view cell implementation in the event that this is causing the error
To do this paste these functions in your project
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
you can play around with the values :)
This can be easily implemented using UITableView. So try to use UITableView instead of UICollectionView.
It looks like you're adding a new label every time you set the text. If you want to lazily load the UILabel you need to add lazy
lazy var interestLabel: UILabel = {
...
}()
I wouldn't do it this way though, I would create a reference to the label
weak var interestLabel: UILabel!
Then add the label in your setupViews method
func setupViews() {
...
let interestLabel = UILabel()
...
contentView.addSubview(interestLabel)
self.interestLabel = interestLabel
}
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.
At UITableView, completely static tableView config is possible. You can disconnect UITableView's datasource and put each cell on storyboard(or xib) by using IB.
I tried same thing with UICollectionView. disconnect UICollectionView's datasource. Put each cell on UICollectionView on storyboard. I built it without any errors. But it didin't work. cells were not displayed at all.
Is UICollectionView without datasource possible?
No.
Creating a static UICollectionViewController is not allowed. You must have a data source delegate.
I also want to point out that there is not a static UITableView, but a static UITableViewController. It's a difference.
You can easily create a static UICollectionViewController.
Just create every cell in interface builder, give them re-use identifiers(e.g. "Home_1" "Home_2" "Home_3"), and populate the methods as follows:
class HomeViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
let cellIdentifiers:[String] = ["Home_1","Home_2","Home_3"]
let sizes:[CGSize] = [CGSize(width:320, height:260),CGSize(width:320, height:160),CGSize(width:320, height:100)]
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellIdentifiers.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifiers[indexPath.item], for: indexPath)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return sizes[indexPath.item]
}
}
Then set the view controller to be of the proper class, and, hey presto, a (basically) static collection. I'm sorry to say but this is BY FAR the best way to support portrait and landscape views when you have groups of controls...
I did a little experimenting and wanted to add my own method since it helped me achieve the truly static, highly custom Collection View I was looking for.
You can create custom UICollectionViewCells for each cell you want to display in your Collection View, and register them with all the Cell IDs in your Collection View, like this:
Create your static cell:
class MyRedCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Make as many of these as you want.
And then back in your Collection View Controller, register them with their corresponding cellId:
let cellIds = ["redCell","blueCell","greenCell"]
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(MyRedCell.self, forCellWithReuseIdentifier: "redCell")
collectionView.register(MyBlueCell.self, forCellWithReuseIdentifier: "blueCell")
collectionView.register(MyGreenCell.self, forCellWithReuseIdentifier: "greenCell")
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIds[indexPath.item], for: indexPath)
return cell
}
Each cell will display exactly what's in its class.