I want to get a JSON response and display data in my viewcontroller.
I have this JSON response:
{"status":1,"data":{"blocks":[{"name":"CustomBlock","description":"CustomDescription","items":[1]}], "items":[{"id:"1", name: "testgame"}]}
I have blocks with name, description, and array of items. Also, all items is passing here with the key "items"
I have created this tableview class
BlocksTableView.swift
class BlocksTableView : UITableView, UITableViewDataSource,
UITableViewDelegate
{
var blocks = [Block]()
var items : BlockItem!
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
self.loadBlocks()
}
func loadBlocks()
{
guard let url = URL(string : "myURL") else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(APIResponse.self, from: data)
self.blocks = response.data.blocks;
self.items = response.data.items;
DispatchQueue.main.async {
self.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return blocks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as? BlockCell else { return UITableViewCell() }
cell.blockName.text = blocks[indexPath.row].name
cell.blockDescription.text = blocks[indexPath.row].description
cell.gameCollectionView.reloadData()
return cell
}
}
Now I want to display items in collectionview inside tableviewcell, but I have no idea how to do this. Since each block has different count of items, I need to pass blocks and items variables to my CollectionViewClass, right? But how to do this properly?
Here is my collectionviewclass
class GameCollectionView : UICollectionView,
UICollectionViewDataSource, UICollectionViewDelegate
{
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//here i need to return number of items in current block, something like blocks[0].items.count
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCollectionCell", for: indexPath) as? GameCollectionCell else { return
UICollectionViewCell()
}
return cell
}
You can try
class BlocksTableView : UITableView, UITableViewDataSource,UITableViewDelegate,UICollectionViewDataSource, UICollectionViewDelegate {
var blocks = [Block]()
var items : BlockItem!
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
self.loadBlocks()
}
func loadBlocks()
{
guard let url = URL(string : "myURL") else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let response = try JSONDecoder().decode(APIResponse.self, from: data)
self.blocks = response.data.blocks;
self.items = response.data.items;
DispatchQueue.main.async {
self.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return blocks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "BlockCell") as? BlockCell else { return UITableViewCell() }
cell.blockName.text = blocks[indexPath.row].name
cell.blockDescription.text = blocks[indexPath.row].description
cell.gameCollectionView.delegate = self
cell.gameCollectionView.dataSource = self
cell.gameCollectionView.tag = indexPath.row
cell.gameCollectionView.reloadData()
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return blocks[collectionView.tag].items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "GameCollectionCell", for: indexPath) as? GameCollectionCell else { return
UICollectionViewCell() }
// here use blocks[collectionView.tag].items[indexPath.row] for each item
return cell
}
}
Related
I have a class, LocationViewController, which needs to implement a TableView. I have a function getParsedTestingLocation() which uses a completion handler from another function to get some data.
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func getParsedTestingLocations(completion: #escaping ([TestingLocation]?, Error?) -> (Void)) {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
completion(testLocations, nil)
} catch {
print(error)
}
}
})
}
}
I want to use the value testLocations within getParsedTestingLocations() in an external extension in this file. Here are the extensions I have:
extension LocationViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me!")
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "empty cell"
return cell
}
}
Within all 3 tableView() functions I want to get the values stored in testLocations in the completion handler in these functions. How could I do this?
Actually you don't need a completion handler. Reload the table view inside the completion closure of the API call
class LocationViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var locationData = [TestingLocation]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
self.locationData = try decoder.decode([TestingLocation].self, from: testLocationsData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}
})
}
}
extension LocationViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let location = locationData[indexPath.row]
cell.textLabel?.text = location.whatEverStringYouWantToDisplay
return cell
}
}
Replace whatEverStringYouWantToDisplay with the real struct member name.
You don't need a completionHandler in getParsedTestingLocations in this case as the function already calls a function which has completionHandler. Just use a variable
class LocationViewController: UIViewController {
private lazy var locationArr = [TestingLocation]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getParsedTestingLocations()
}
func getParsedTestingLocations() {
getTestingLocationsJSON(completion: { testLocationsJSON, error in
if let testLocationsJSON = testLocationsJSON {
let testLocationsData = Data(testLocationsJSON.utf8)
let decoder = JSONDecoder()
do {
let testLocations = try decoder.decode([TestingLocation].self, from: testLocationsData)
self.locationArr = testLocations
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let error {
//Show Alert
}
}
})
}
}
extension LocationViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = locationArr[indexPath.row].variable
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("you tapped me! \(locationArr[indexPath.row])")
}
}
I have a ViewController class embedded with tableView in which I created two cells
First:
class CategoryTableViewCell: UITableViewCell {
//MARK:- IBOUTLETS
//MARK:-
#IBOutlet weak var collectionView: UICollectionView!
var categoryArray: [PopularCategories]! {
didSet {
self.collectionView.reloadData()
}
}
override func awakeFromNib() {
super.awakeFromNib()
collectionView.delegate = self
collectionView.dataSource = self
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: true)
}
}
In which I created I created a CollectionViewCell.
And in my 2nd TableViewCell class I reloaded the data which is coming from the api.
This is collectionView code inside TableViewCell class
extension CategoryTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return categoryArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "CatergoriesCollectionViewCell", for: indexPath) as? CatergoriesCollectionViewCell else {
return UICollectionViewCell()
}
cell.nameLabel.text = categoryArray[indexPath.item].name
cell.image.sd_setImage(with: URL(string: categoryArray[indexPath.item].image ), placeholderImage: UIImage(named: "placeholderSmall"))
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cell = self.collectionView.dequeueReusableCell(withReuseIdentifier: "CatergoriesCollectionViewCell", for: indexPath) as! CatergoriesCollectionViewCell
collectionCellTapHandler?()
let id = categoryArray[indexPath.item].id
self.categroyID = id
controller.categoryId = id
controller.filterDataUsingMostPopularCategory(id: id, lat: Latitude, long: Longitude)
print("Here I can access my view controller....\(controller.categoryId)")
print(cell.nameLabel.text!, id)
}
}
}
Now what I want I need to call a function which is in my ViewController when select a collectionView cell item. This the function in my ViewController class file I want to access when collectionViewCell is selected
class OneStopShopVC: TruckerConveyBaseVC {
func searchDataFromFilteredApi() {
let param: [String : Any] = ["lat": self.latitude, "lng": self.longitude, "title": selectedTitle, "category": "\(selectedCategory)"]
print(param)
CommonUtils.showHudWithNoInteraction(show: true)
Alamofire.request(Constants.BASE_URL+"search_home_ads.php", method: .post, parameters: param, encoding: URLEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in
CommonUtils.showHudWithNoInteraction(show: false)
switch(response.result) {
case .success(_):
if let json = response.result.value as? [String:Any] {
print(json)
if let ads_list = json["ads_list"] as? [[String:Any]] {
self.adsListModel.removeAll()
let response = kSharedInstance.getArray(withDictionary: ads_list)
print(response)
self.adsListModel = response.map{ AdsListModel(with: $0) }
}
DispatchQueue.main.async {
self.reloadList()
}
}
break
case .failure(_):
print("Error")
break
}
}
}
}
Here is code inside UITableViewDataSource and Delegate
extension OneStopShopVC : UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 1
} else {
return Int.getInt(self.adsListModel.count)
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 181
} else {
return 121
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return cellConfig(indexPath)
}
private func cellConfig(_ indexpath : IndexPath) -> UITableViewCell {
if indexpath.section == 0 {
guard let cell = oneStopShopTableView.dequeueReusableCell(withIdentifier: CategoryTableViewCell.cellIdentifier()) as? CategoryTableViewCell else {
return UITableViewCell()
}
cell.categoryArray = popularCategories
cell.collectionCellTapHandler = {[weak self] in
self?.filterDataUsingMostPopularCategory(id: cell.categroyID, lat: Latitude, long: Longitude)
}
cell.collectionView.reloadData()
return cell
}
else {
let cell = oneStopShopTableView.dequeueReusableCell(withIdentifier: OneStopShopTableCell.cellIdentifier()) as! OneStopShopTableCell
cell.lblPostTitle.text = String.getString(self.adsListModel[indexpath.row].post_title)
cell.lblPostedDate.text = String.getString(self.adsListModel[indexpath.row].posted_date)
cell.lblPostedExpDate.text = String.getString(self.adsListModel[indexpath.row].posted_expired_date)
cell.lblPostedDesc.text = String.getString(self.adsListModel[indexpath.row].post_desc)
cell.postedImage.sd_setImage(with: URL(string: adsListModel[indexpath.row].post_image ?? ""), placeholderImage: UIImage(named: ""))
let status = String.getString(self.adsListModel[indexpath.row].status)
if (status == "Publish") {
cell.statusLabel.text = "Published"
cell.statusLabel.textColor = #colorLiteral(red: 0.2745098174, green: 0.4862745106, blue: 0.1411764771, alpha: 1)
}
else if(status == "Banned") {
cell.statusLabel.textColor = UIColor.darkGray
}
else {
cell.statusLabel.textColor = UIColor.red
}
cell.priceLabel.text = "$\(String.getString(self.adsListModel[indexpath.row].price))"
return cell
}
}
Conclusion: When I click on CollectionViewCell item in first TableViewCell class I want to reload the data of SecondTableViewCell.. For that I need to access ViewController function to reload data. How can I do this?
In general, you have multiple options on how to solve this, you need to choose one of these based on different criteria.
The first option is as the answer before creating a closure function and assigns it to the cell from the viewController.
The second option is to implement a delegate pattern like this:
protocol MyDelegate:class {
func doTheJob()
}
class CategoryTableViewCell: UITableViewCell, UICollectionViewDelegate {
//rest of the code...
weak var myDelegate:MyDelegate? = nil
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
myDelegate?.doTheJob()
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CategoryTableViewCell
cell.myDelegate = self
}
extension OneStopShopVC: MyDelegate {
func doTheJob() {
}
}
The third option can be to have one class which will in charge of such logic some kind of manager class. This class can be a singleton and you can instantiate from where you need it.
In general, you have a lot of solutions for this. But you need to think what is your need and to separate the code in the best way. Think about MVC, MVVM, VIPER or whatever you follow what are the basic principles of separations.
P.S you using an instance of UITableViewCell which is a view, as a ViewController this should turn big red flag for you that your architecture is not okay.
Use closure to handle this.
Create a closure named collectionCellTapHandler in CategoryTableViewCell and call it from collectionView(_: didSelectItemAt:) method.
class CategoryTableViewCell: UITableViewCell, UICollectionViewDelegate {
//rest of the code...
var collectionCellTapHandler: (()->())?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionCellTapHandler?()
}
}
In the above code, I've used a closure accepting 0 arguments. You can modify that as per your requirement.
Now set the collectionCellTapHandler in the ViewController in UITableViewDataSource's tableView(_: cellForRowAt:) method and call your custom method callTheMethod() from it.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CategoryTableViewCell
cell.collectionCellTapHandler = {[weak self] in
self?.callTheMethod()
}
return cell
}
func callTheMethod() {
print("Hello...")
}
Another way of using protocol design pattern, define a CategoryCollectionViewDelegate protocol
protocol CategoryCollectionViewDelegate {
/// You can define parameters as per your need.
func didSelectCategory(_ index: Int)
}
Now in CategoryTableViewCell
extension CategoryTableViewCell: UICollectionViewDataSource, UICollectionViewDelegate {
var delegate_collection: CategoryCollectionViewDelegate?
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.delegate_collection. didSelectCategory(indexPath.item)
}
}
Now in the ViewController in UITableViewDataSource's tableView(_: cellForRowAt:) method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CategoryTableViewCell
cell.delegate_collection = self
return cell
}
func didSelectCategory(_ index: Int){
print("array item index \(index)")
}
Try this.
You can pass the viewcontroller in cellforrow in your tableview
let cell = UITableViewCell()
cell.parentVC = self
return cell
then in you tableviewcell while loading collectionview you can similarly pass viewcontroller
cell.parentVC = parentVC
This works as i have implemented similar thing in my project.
you can use NotificationCenter for this kind of flow. A notification dispatch mechanism that enables the broadcast of information to registered observers.
Click the link for reference.
I am currently having an issue with UICollectionViewCell and when it gets loaded.
Here is the link to the video to see it in action
Below is my code in viewDidLoad i call
retrieveUrls { (success) in
if success {
self.filterImageLabel(handler: { (success) in
if success {
if self.spinner != nil {
self.spinner.stopAnimating()
self.spinner.isHidden = true
}
self.collectionView.reloadData()
}
})
}
}
In retrieveUrls i am parsing the Url to retrieve image URL and for filterImageLabel i set up an photoUrlArray to use with collectionView indexpath
func filterImageLabel(handler: #escaping (_ status: Bool) -> ()) {
photoUrlArray = photoUrlArray.filter {$0.label == "Large Square"}
if photoUrlArray.count > 0 {
handler(true)
}
}
Methods for CollectionView
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoUrlArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? PhotoCell {
cell.updateCellImage(imageUrl: self.photoUrlArray[indexPath.row].source)
return cell
}
return PhotoCell()
}
And Finally in photocell class i am setting up the image
override func prepareForReuse() {
super.prepareForReuse()
photoImg.image = nil
}
func updateCellImage(imageUrl : String) {
Alamofire.request(imageUrl).responseImage(completionHandler: { (response) in
guard let image = response.result.value else {return}
self.photoImg.image = image
})
}
I have looked at various different thread on stack-overflow. however cannot seem to resolve the issue.
Any ideas would be helpful.
Method for CollectionView is as follows:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? PhotoCell {
// ********* ISSUE IS HERE *********
let imageurl = URL(string: self.photoUrlArray[indexPath.row].source)
cell.photoImg.af_setImage(withURL: imageurl!)
return cell
}
return PhotoCell()
}
I have a TableView with two kind of Cells, both are filled with a CollectionView. In the TableViewController I let them them display with a simple if Statement.
My TableViewController:
import UIKit
import RealmSwift
import Alamofire
import SwiftyJSON
let myGroupLive = DispatchGroup()
let myGroupCommunity = DispatchGroup()
class HomeVTwoTableViewController: UITableViewController {
var headers = ["Live", "Channel1", "ChannelTwo", "Channel3", "Channel4", "Channel5", "Channel6"]
override func viewDidLoad() {
super.viewDidLoad()
DataController().fetchSomeDate(mode: "get")
DataController().fetchSomeOtherData(mode: "get")
}
//MARK: Custom Tableview Headers
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return headers[section]
}
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int){
view.tintColor = UIColor.black
let header = view as! UITableViewHeaderFooterView
if section == 0 {
header.textLabel?.textColor = UIColor.black
view.tintColor = UIColor.white
}
else {
view.tintColor = UIColor.groupTableViewBackground
}
}
//MARK: DataSource Methods
override func numberOfSections(in tableView: UITableView) -> Int {
return headers.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
//Choosing the responsible PrototypCell for the Sections
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellBig", for: indexPath) as! HomeVTwoTableViewCell
return cell
}
else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellSmall", for: indexPath) as! HomeVTwoTableViewCellSmall
return cell
}
}
//Set custom cell height, has to match the CollectionView height
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 225.0
}
else {
return 120.0
}
}
}
My TableViewCellSmall:
import UIKit
import RealmSwift
var communities: Results<Community>?
class HomeVTwoTableViewCellSmall: UITableViewCell{
#IBOutlet weak var collectionView: UICollectionView!
}
extension HomeVTwoTableViewCellSmall: UICollectionViewDataSource,UICollectionViewDelegate {
//MARK: Datasource Methods
func numberOfSections(in collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return (communities?.count)!
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellSmall", for: indexPath) as? HomeVTwoCollectionViewCellSmall else
{
fatalError("Cell has wrong type")
}
//Here I want my Sorting Statement to make unique content per collection view
//normal approach if no section is asked
let url : String = (communities?[indexPath.row].pictureUri)!
let name :String = (communities?[indexPath.row].communityName)!
cell.titleLbl.text = name
cell.imageView.downloadedFrom(link :"somelink")
return cell
}
//MARK: Delegate Methods
override func layoutSubviews() {
myGroupCommunity.notify(queue: DispatchQueue.main, execute: {
let realm = try! Realm()
communities = realm.objects(Community.self)
self.collectionView.dataSource = self
self.collectionView.delegate = self
})
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
do something
}
}
My Problem is now, I want the "Channel Cells" to fill with customized and different data, in the CollectionView. That means I need some sort of key to get the right data in the right cell. My approach would be to take the SectionHeader Title, but for some reasons I cant access it from the TableViewCellSmall. So I have all the data in all the Cells and cant sort them without my Key.
Thanks in Advance.
from what I understand you need to fill the collectionview of each cell with different contents and for this needs to identify the cell?
If so, I used the method below that helped me, you can try.
If in doubt let me know so I can help, I hope I have helped :)
//TableViewCell Add
var didCollectionViewCellSelect: ((Int) -> Void)?
override func setSelected(_ selected: Bool, animated: Bool)
{
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//TabelView Add
class myClass: UITableViewController
{
var storedOffsets = [Int: CGFloat]()
override func viewDidLoad()
{
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
guard let tableViewCell = cell as? myTableViewCell else { return }
let secao = indexPath.section*1000 //Section
let linha = indexPath.row //Row
let posicao = secao+linha
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: posicao)
tableViewCell.collectionViewOffset = storedOffsets[posicao] ?? 0
}
override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
guard let tableViewCell = cell as? myTableViewCell else { return }
let secao = indexPath.section*1000 //Section
let linha = indexPath.row //Row
let posicao = secao+linha
storedOffsets[posicao] = tableViewCell.collectionViewOffset
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
let posicao = collectionView.tag
let secao = Int(collectionView.tag/1000) //Section
let linha = posicao-(secao*1000) //Row
var qtd = 0
if secao == 0 && arrStation.count > 0
{
qtd = arrStation.count
}
return qtd
}
I have a View Controller that contains a table and a collection view in the prototype cell. I have set the VC as the both the DataSource and Delegate for the Table but my question is what do I set as the Datasource and delegate of the collectionView
My code works fine when I simply hardcode the number of sections etc for the collection view. However when I try read an array from a plist file something goes wrong and the same images are displayed for each section
enter image description here
Here is the code for the tableViewCell
import UIKit
class CategoryRow: UITableViewCell {
var pictDataSource = PicDataSource()
}
// Mark: Set-up the CollectionView
extension CategoryRow: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return pictDataSource.numberOfSections
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return pictDataSource.numberOfPicsInSection(section)
// return 12
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath) as! CategoryRowCollectionViewCell
if let picc = pictDataSource.picForItemAtIndexPath(indexPath) {
cell.pic = picc
}
configreCell(cell, atIndexPath: indexPath)
return cell
}
func configreCell(_ cell: CategoryRowCollectionViewCell, atIndexPath indexPath: IndexPath) {
cell.cellLabel.text = "\(indexPath.row)"
}
}
// Mark: Create the CollectionView flow layout
extension CategoryRow: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let itemsPerRow: CGFloat = 4
let hardCodedPadding: CGFloat = 5
let itemWidth = (collectionView.bounds.width/itemsPerRow) - hardCodedPadding
let itemHeight = collectionView.bounds.height - (2 * hardCodedPadding)
return CGSize(width: itemWidth, height: itemHeight)
}
}
And this is the code to read from dataSource
//
// PicDataSource.swift
import Foundation
import UIKit
class Pict {
var caption: String
var imageName: String
var section: String
var index: Int
init(caption: String, imageName: String, section: String, index: Int) {
self.caption = caption
self.imageName = imageName
self.section = section
self.index = index
}
convenience init(copying pic: Pict) {
self.init(caption: pic.caption, imageName: pic.imageName, section: pic.section, index: pic.index)
}
}
class PicDataSource {
var pics: [Pict] = []
var sections: [String] = []
var immutablePics: [Pict] = []
var count: Int {
return pics.count
}
var numberOfSections: Int {
return sections.count
}
init() {
pics = loadImagesFromDisk()
immutablePics = pics
}
func loadImagesFromDisk() -> [Pict] {
sections.removeAll(keepingCapacity: false)
if let path = Bundle.main.path(forResource: "Images", ofType: "plist") {
if let dictArray = NSArray(contentsOfFile: path) {
// print ("DICTARRAY COUNT", dictArray.count)
var pics: [Pict] = []
for item in dictArray {
if let dict = item as? NSDictionary {
let caption = dict["caption"] as! String
let imageName = dict["imageName"] as! String
let section = dict["section"] as! String
let index = dict["index"] as! Int
let pic = Pict(caption: caption, imageName: imageName, section: section, index: index)
if !sections.contains(section) {
sections.append(section)
}
pics.append(pic)
}
}
return pics.sorted { $0.section < $1.section }
}
}
return []
}
func numberOfPicsInSection(_ index: Int) -> Int {
let pics = picsForSection(index)
return pics.count
}
func picForItemAtIndexPath(_ indexPath: IndexPath) -> Pict? {
if indexPath.section > 0 {
let picss = picsForSection(indexPath.section)
return picss[indexPath.item]
} else {
return pics[indexPath.item]
}
}
func titleForSectionAtIndexPath(_ indexPath: IndexPath) -> String? {
if indexPath.section < sections.count {
return sections[indexPath.section]
}
return nil
}
func picsForSection(_ index: Int) -> [Pict] {
let section = sections[index]
let picsInSection = pics.filter { (pic: Pict) -> Bool in
return pic.section == section
}
return picsInSection
}
}
The ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
// var categories = ["Action", "Drama", "Science Fiction", "Kids", "Horror"]
var categories = PicDataSource()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return categories.numberOfSections
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories.sections[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CategoryRow
return cell
}
}
If anyone has any ideas or pointers as to what I'm doing wrong I be gratefull
You set the collection view's dataSource and delegate to whatever class will be implementing the UICollectionViewDataSource and UICollectionViewDelegate methods. It's likely this will be the same view controller used as your table view's dataSource and delegate but it doesn't have to be.