I'm new to swift as well as iOS development.I wanna create tableview containing collection view in swift. that i'm able to do so far but my problem arises when the number of items in collection view and no. of rows in tableview depends on the json data i'm retrieving. I'm not able to program number of items in collection view. I'm using custom cell classes for tableviewcell as well as collectionviewcell. How can i change data in collectionview cell of table each table row differently.code for collection view delegates
func collectionView(collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return array_servicelist.objectAtIndex(something).valueForKey("subCategoryList")!.count
}
func collectionView(collectionView: UICollectionView,
cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("collectioncell",
forIndexPath: indexPath) as! ServiceCollectionViewCell
let str :String = "\(array_servicelist.objectAtIndex(indexPath.section).valueForKey("subCategoryList")!.objectAtIndex(indexPath.item).valueForKey("phoneImageUrl") as! String)"
cell.serviceImage.image = UIImage(data: NSData(contentsOfURL: NSURL(string: str)!)!)
cell.serviceName.text = "\(array_servicelist.objectAtIndex(indexPath.section).valueForKey("subCategoryList")!.objectAtIndex(indexPath.item).valueForKey("categoryName") as! String)"
return cell
}
code for tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return array_servicelist.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("tablecell", forIndexPath: indexPath)
return cell
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
guard let tableViewCell = cell as? ServiceTableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
This is an example of collectionView inside a collectionViewCell, but the same applies to tableView's as well. The datasource is abstracted, each cell has its own datasource. You should be able to figure out your problem looking at this.
https://github.com/irfanlone/Collection-View-in-a-collection-view-cell
Using a collection view has certainly some advantages over tableviews in this case. Other useful Links:
Tutorial : https://ashfurrow.com/blog/putting-a-uicollectionview-in-a-uitableviewcell-in-swift/
Source Code: https://github.com/ashfurrow/Collection-View-in-a-Table-View-Cell
Hope that helps!!
Related
After the tableView.reloadData() the visible collectionView display the first row unexpected immediately.
Im building a tableView contains collectionView in its cells, users can scroll multiple images in every single tableView just like Instagram. How can I fix it? Thanks!
tableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return photoRolls.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as! HomeTableViewCell
if photoRolls.isEmpty {
return UITableViewCell()
}
let user: UserModel = users[indexPath.row]
let photoRoll: PhotoRoll = photoRolls[indexPath.row] //this Model contains post info: likes, comments etc.
let photoUrls: UrlStrings = urls[indexPath.row] //this Model contains a array of urlStrings for each collectionView inside the tableViewCell
cell.urlStrings = photoUrls
cell.photoRoll = photoRoll
cell.user = user
cell.delegate = self
return cell
}
prepareForReuse Method in tableViewCell
override func prepareForReuse() {
super.prepareForReuse()
captionLabel.text = nil
profileImage.image = UIImage(named: "placeholderImg")
profileImageRight.image = UIImage(named: "placeholderImg")
collectionView.scrollToItem(at:IndexPath(item: 0, section: 0), at: .left, animated: false)//tried to remove this method, but the collectionView would not display the first row when it's visible
}
DataSource of collectionView inside tableViewCell
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellUrlArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeCollectionViewCell", for: indexPath) as! HomeCollectionViewCell
cell.url = cellUrlArray[indexPath.row]
return cell
}
Like the question title said. I expect the visible collectionView stays on the current row after the tableView load more data after tableView.reloadData() is called! Thanks again!
I think it is possible with contentOffset cacheing, like below
var cachedPosition = Dictionary<IndexPath,CGPoint>()
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if let cell = cell as? HomeTableViewCell {
cachedPosition[indexPath] = cell.collectionView.contentOffset
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
<<Your Code>>
cell.collectionView.contentOffset = cachedPosition[indexPath] ?? .zero
return cell
}
I have 2 prototype dynamic cell called InvoiceDetailCell and TotalCostFooterCell. I make the TotalCostFooterCell as the Footer Cell View using viewForFooterInSection. here is the code I use to assign data to the UITableView
here is my code in UITableViewController.
extension InvoiceDetailVC : UITableViewDataSource {
// MARK: - UI Table View Datasource Methods
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return invoiceElements.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "InvoiceDetailCell", for: indexPath) as! InvoiceDetailCell
cell.invoiceElementData = invoiceElements[indexPath.row]
return cell
}
}
extension InvoiceDetailVC : UITableViewDelegate {
// MARK: - UI Table View Delegate Methods
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "invoiceDetailFooterCell") as! TotalCostFooterCell
cell.totalCost = singleInvoiceData.unpaid
return cell
}
}
but the result is not as I expected, I mean the footer cell is stick / not move. here is the .gif file : http://g.recordit.co/vf0iwCfEWX.gif
you can see the total cost (red colour) is sticky / static, I want that footer cell can be scrolled and always on the bottom. or do I have the wrong to implement what I want?
make the table style to grouped
you can do it in two ways:
In viewDidLoad() do tableView.style = .grouped
Select the table view from storyboard and in the attribute inspector change the style to grouped. Please refer attached image.
Can't you just make it as the last row of the table view? I mean, the view is already a table view cell, so it makes sense to use it as the last row.
First change this:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return invoiceElements.count + 1
}
And then for cellForRowAt:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == invoiceElements.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "invoiceDetailFooterCell") as! TotalCostFooterCell
cell.totalCost = singleInvoiceData.unpaid
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "InvoiceDetailCell", for: indexPath) as! InvoiceDetailCell
cell.invoiceElementData = invoiceElements[indexPath.row]
return cell
}
You can set your custom view that you want by:
tableView.tableFooterView = yourCustomView
Or you can put everything inside your xib/storyboard like this:
I followed Ash Furrows tutorial collectionView inside tableView
I have TableItem class that has a name property and an array of URLs. I have a tableView that has collectionView inside of it.
I vertically populate my tableview with the whatever amount of TableItems are available and I horizontally populate my collectionView with however many amount of urls that are inside the array from the TableItem class.
I'm not concerned with pressing a table cell and then getting the information, I want the data to be displayed simultaneously in both collections.
How can I pull the TableItem data and display it in the collectionView at the same time as the tableView?
class TableItem{
var name: String?
var urlsForCollection: [URl] = []
}
var tableItems = [TableItem]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableCell", for: indexPath) as! TableCell
cell.textLabel.text = tableItems[indexPath.row].name
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//how do I return the urlsForCollection.count from each TableItem in tableItems
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionCell", for: indexPath) as! CollectionCell
//how can I access the urlsForCollection for each TableItem in tableItems
for url in urlsForCollection{
imageView.sd_setImage(with: url!)
}
return cell
}
Like this:
let tableItem = tableItems[indexPath.row] as! TableItem
for url in tableItem.urlsForCollection{
imageView.sd_setImage(with: url!)
}
Should work.
am trying to embed a UIStackView inside of a UITableViewCell. Inside of the UIStackView should be two items: a UICollectionView and another UITableView.
The approach I am taking is to embed everything inside of a UIView contained in a root UIViewController.
I have created a dataSource outlet from the parent UITableView and made the root UIViewController conform to UITableViewDataSource. Afterwards, I implemented the standard UITableView functions and did a similar thing for the embedded UICollectionView in the UITableViewCell. Here is the code for the root UIViewController and custom UITableViewCell:
class OverviewController: UIViewController, UITableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "AppTitle"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "appCell", for: indexPath) as! AppCell
return cell
}
}
class AppCell: UITableViewCell, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "featureCell", for: indexPath) as! AppCollectionCell
return cell
}
}
When I run this, I just get an empty table view:
What am I doing wrong?
I made a little project for you: https://github.com/mvdizel/q44833725
Use same dataSource's references as in project
Be careful with implementing data source methods
Your top table view has data source - your view controller.
Other one table view (inside the top table view cell) and collection view, both has data source - your cell.
So i am trying to get the value of the textLabel of the row I select. I tried printing it, but it didn't work. After some research I found out that this code worked, but only in Objective-C;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"did select and the text is %#",[tableView cellForRowAtIndexPath:indexPath].textLabel.text);]
}
I could not find any solution for Swift. Printing the indexpath.row is possible though, but that is not what I need.
so what should I do? or what is the 'Swift-version' of this code?
Try this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow() //optional, to get from any UIButton for example
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell
print(currentCell.textLabel!.text)
If you're in a class inherited from UITableViewController, then this is the swift version:
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = self.tableView.cellForRowAtIndexPath(indexPath)
NSLog("did select and the text is \(cell?.textLabel?.text)")
}
Note that cell is an optional, so it must be unwrapped - and the same for textLabel. If any of the 2 is nil (unlikely to happen, because the method is called with a valid index path), if you want to be sure that a valid value is printed, then you should check that both cell and textLabel are both not nil:
override func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = self.tableView.cellForRowAtIndexPath(indexPath)
let text = cell?.textLabel?.text
if let text = text {
NSLog("did select and the text is \(text)")
}
}
Swift 4
To get the label of the selected row:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
print(cell.textLabel?.text)
}
To get the label of the deselected row:
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! TableViewCell
print(cell.textLabel?.text)
}
If you want to print the text of a UITableViewCell according to its matching NSIndexPath, you have to use UITableViewDelegate's tableView:didSelectRowAtIndexPath: method and get a reference of the selected UITableViewCell with UITableView's cellForRowAtIndexPath: method.
For example:
import UIKit
class TableViewController: UITableViewController {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
switch indexPath.row {
case 0: cell.textLabel?.text = "Bike"
case 1: cell.textLabel?.text = "Car"
case 2: cell.textLabel?.text = "Ball"
default: cell.textLabel?.text = "Boat"
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedCell = tableView.cellForRowAtIndexPath(indexPath)
print(selectedCell?.textLabel?.text)
// this will print Optional("Bike") if indexPath.row == 0
}
}
However, for many reasons, I would not encourage you to use the previous code. Your UITableViewCell should only be responsible for displaying some content given by a model. In most cases, what you want is to print the content of your model (could be an Array of String) according to a NSIndexPath. By doing things like this, you will separate each element's responsibilities.
Thereby, this is what I would recommend:
import UIKit
class TableViewController: UITableViewController {
let toysArray = ["Bike", "Car", "Ball", "Boat"]
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toysArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.textLabel?.text = toysArray[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let toy = toysArray[indexPath.row]
print(toy)
// this will print "Bike" if indexPath.row == 0
}
}
As you can see, with this code, you don't have to deal with optionals and don't even need to get a reference of the matching UITableViewCell inside tableView:didSelectRowAtIndexPath: in order to print the desired text.
In my case I made small changes, when i search the value in tabelview select (didSelectRowAtIndexPath) the cell its return the index of the cell so im get problem in move one viewControler to another.By using this method i found a solution to redirect to a new viewControler
let indexPath = tableView.indexPathForSelectedRow!
let currentCellValue = tableView.cellForRow(at: indexPath!)! as UITableViewCell
let textLabelText = currentCellValue.textLabel!.text
print(textLabelText)
In swift 4 :
by overriding method
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let storyboard = UIStoryboard(name : "Main", bundle: nil)
let next vc = storyboard.instantiateViewController(withIdentifier: "nextvcIdentifier") as! NextViewController
self.navigationController?.pushViewController(prayerVC, animated: true)
}
This will work:
let item = tableView.cellForRowAtIndexPath(indexPath)!.textLabel!.text!
Maintain an array which stores data in the cellforindexPath method itself :-
[arryname objectAtIndex:indexPath.row];
Using same code in the didselectaAtIndexPath method too.. Good luck :)