Hello I have a tableview inside horizontal scrolling collection view as shown below.
http://recordit.co/pUyTbuOPsn
TableView cells get populated properly by tvdatasource but
since TableView is embedded in a CollectionViewCell methods such as didselectitematindexpath does not get called.
how can I properly setup tableviewdelegate methods with controller and collection view cell so that delegate methods can funnel through to the top controller?
its easy to populate but CALLING the methods are what I need help with :D
here is the code below
also I will be reusing this pattern all over the app. many tableviews won't be static and data will be dynamically fed from the API.
class AddItemViewController: UIViewController,
UICollectionViewDelegate, UICollectionViewDataSource,
UICollectionViewDelegateFlowLayout
{
#IBOutlet weak var mainCollectionView: UICollectionView!
var currentCellPosition = 0
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.tintColor = UIColor.leafBlack()
automaticallyAdjustsScrollViewInsets = false
mainCollectionView.dataSource = self
mainCollectionView.delegate = self
mainCollectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
mainCollectionView.register(AddItemDetailsCollectionViewCell.self, forCellWithReuseIdentifier: "DetailsCell")
mainCollectionView.register(AddItemPhotoCollectionViewCell.self, forCellWithReuseIdentifier: "PhotoCell")
mainCollectionView.register(AddItemShippingCollectionViewCell.self, forCellWithReuseIdentifier: "ShippingCell")
if let flowLayout = mainCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
flowLayout.itemSize = mainCollectionView.frame.size
flowLayout.minimumInteritemSpacing = 0
flowLayout.sectionInset = UIEdgeInsets.zero
flowLayout.footerReferenceSize = CGSize.zero
flowLayout.headerReferenceSize = CGSize.zero
}
mainCollectionView.isPagingEnabled = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
if(indexPath.row == 0) {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailsCell", for: indexPath) as! AddItemDetailsCollectionViewCell
}else if(indexPath.row == 1) {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PhotoCell", for: indexPath) as! AddItemPhotoCollectionViewCell
}else if(indexPath.row == 2) {
cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ShippingCell", for: indexPath) as! AddItemShippingCollectionViewCell
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("yo")
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize.init(width: view.frame.width, height: view.frame.height)
}
#IBAction func showPrevCell(_ sender: Any) {
if (currentCellPosition > 0) {
currentCellPosition -= 1
mainCollectionView.scrollToItem(at: IndexPath.init(row: currentCellPosition, section: 0), at: .centeredHorizontally, animated: true)
}
}
#IBAction func showNextCell(_ sender: Any) {
if (currentCellPosition < 2) {
currentCellPosition += 1
mainCollectionView.scrollToItem(at:IndexPath.init(row: currentCellPosition, section: 0), at: .centeredHorizontally, animated: true)
}
}
}
import Foundation
import UIKit
class AddItemDetailsCollectionViewCell: BaseAddItemCollectionViewCell {
#IBOutlet weak var cellTitleLabel: UILabel!
#IBOutlet weak var mainTableView: UITableView!
var detailFieldNames = ["Model Name", "Brand", "Size", "Description", "Condition"]
var containingVC: AddItemViewController?
override var nibName: String! {
get {
return "AddItemDetailsCollectionViewCell"
} set {}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
configView()
}
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
configView()
}
override func configView() {
mainTableView.dataSource = self
mainTableView.delegate = self
mainTableView.separatorStyle = .none
mainTableView.allowsSelection = false
let attributes = [NSFontAttributeName: UIFont(name: "Rokkitt-Thin", size: 22)!, NSForegroundColorAttributeName: UIColor.leafBlack(), NSKernAttributeName : 3.0] as [String : Any]
cellTitleLabel.attributedText = NSAttributedString(string: "Detail Info", attributes: attributes)
mainTableView.register(AddDetailTableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
extension AddItemDetailsCollectionViewCell: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! AddDetailTableViewCell
cell.checkBox.setCheckState(.mixed, animated: true)
//
}
}
extension AddItemDetailsCollectionViewCell: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? AddDetailTableViewCell {
cell.setupTitleLabel(detailFieldNames[indexPath.row])
return cell
}
return UITableViewCell()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
}
class BaseAddItemCollectionViewCell: UICollectionViewCell {
var cell: UICollectionViewCell!
var nibName: String!
override init(frame: CGRect) {
super.init(frame: frame)
xibSetup()
configView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
xibSetup()
configView()
}
func xibSetup() {
do {
try cell = loadCellFromNib()
// use bounds not frame or it'll be offset
cell.frame = bounds
// Make the view stretch with containing view
cell.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]
// Adding custom subview on top of our view (over any custom drawing > see note below)
addSubview(cell)
} catch {
print("Failed to create view from nib")
}
}
func loadCellFromNib() throws -> UICollectionViewCell {
let bundle = Bundle.main
let nib = UINib(nibName: nibName, bundle: bundle)
let cell = (nib.instantiate(withOwner: self, options: nil)[0] as? UICollectionViewCell)!
return cell
}
func configView() {
}
}
Related
I have a UICollectionView placed inside a UITableViewCell. The collection view has its scroll direction set to horizontal. However, when I swipe left or right on it to try and scroll, it doesn't work. Instead the table view cell is just pressed. It seems like the table view is eating up the gesture and not allowing the collection view to register it.
How can I make it so the collection view can scroll left and right with horizontal swipes, while still allowing the parent table view to scroll vertically?
Image that I want to the first cell to have a collection view of images horizontally and on the other cells rows of names for example .
you can see my project on this GitHub account : https://github.com/BenSeferidis/Nft-Assets/tree/version4 for better understanding .
the code is the following :
LobbyViewController (Main VC):
import UIKit
class LobbyViewController: UIViewController {
// MARK: - IBProperties
#IBOutlet weak var tableView: UITableView!
// MARK: - Properties
var data: [DataEnum] = []
var likes:[Int] = []
var numlikes: Int = 0
var nfts: [Nft] = []
let creators : [Creator] = []
var icons: [Icon] = []
var loadData = APICaller()
// MARK: - Life Cyrcle
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "AssetTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "AssetTableViewCell")
let nib2 = UINib(nibName: "CreatorsTableViewCell", bundle: nil)
tableView.register(nib2, forCellReuseIdentifier: "CreatorsTableViewCell")
tableView.dataSource = self //method to generate cells,header and footer before they are displaying
tableView.delegate = self //method to provide information about these cells, header and footer ....
downloadJSON {
self.tableView.reloadData()
print("success")
}
loadData.downloadData { (result) in
print(result)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PresentViewController {
destination.nft = nfts[tableView.indexPathForSelectedRow!.row]
destination.delegate = self
}
}
// MARK: - Methods
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://public.arx.net/~chris2/nfts.json")
URLSession.shared.dataTask(with: url!) { [self] data, response, error in
if error == nil {
do {
self.nfts = try JSONDecoder().decode([Nft].self, from: data!)
nfts.forEach { nft in
let creators = nfts.map (\.creator)
self.data.append(.type1(creators: creators))
}
self.nfts.forEach { nft in
self.data.append(.type2(nft: nft))
}
DispatchQueue.main.async {
completed()
}
}
catch {
print("error fetching data from api")
}
}
}.resume()
}
}
// MARK: - Extensions
extension LobbyViewController : UITableViewDelegate , UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
indexPath.row == 0 ? 100 : UITableView.automaticDimension
}
//gemizo ta rows tou table
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch self.data[indexPath.item] {
case .type1(let creators):
let cell = tableView.dequeueReusableCell(withIdentifier: "CreatorsTableViewCell",
for: indexPath) as! CreatorsTableViewCell
//cell.creatorsCollectionView = self
return cell
case .type2(let nft):
let cell = tableView.dequeueReusableCell(withIdentifier: "AssetTableViewCell",
for: indexPath) as! AssetTableViewCell
cell.nameLabel?.text = nft.name
cell.nameLabel.layer.cornerRadius = cell.nameLabel.frame.height/2
cell.likesLabel?.text = "\((numlikes))"
let imgUrl = (nft.image_url)
print(imgUrl)
cell.iconView.downloaded(from: imgUrl)
cell.iconView.layer.cornerRadius = cell.iconView.frame.height/2
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
}
extension LobbyViewController : TestDelegate{
func sendBackTheLikess(int: Int) {
numlikes = int
tableView.reloadData()
}
}
// MARK: - Enums
enum DataEnum {
case type1(creators: [Creator])
case type2(nft: Nft)
}
// MARK: - Struct
struct Constants {
static let url = "https://public.arx.net/~chris2/nfts.json"
}
Creators TableViewCell :
import UIKit
class CreatorsTableViewCell: UITableViewCell {
//MARK: - IBProtperties
#IBOutlet var creatorsCollectionView: UICollectionView!
#IBOutlet var creatorsLbl: UILabel!
//MARK: - Properties
var nft : Nft?
var creators : [Creator] = []
var users: User?
weak var delegate : CreatorsTableViewCellDelegate?
//MARK: - Life Cyrcle
override func awakeFromNib() {
super.awakeFromNib()
creatorsCollectionView.dataSource = self
creatorsCollectionView.delegate = self
let nibName = UINib(nibName: "CollectionViewCell", bundle: nil)
creatorsCollectionView.register(nibName, forCellWithReuseIdentifier: "CollectionViewCell")
creatorsCollectionView.horizontalScrollIndicatorInsets
}
//init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: 10 , height: 10)
layout.sectionInset = UIEdgeInsets(top: 3, left: 3, bottom: 3, right: 3)
super.init(style: style, reuseIdentifier: reuseIdentifier)
creatorsCollectionView.showsHorizontalScrollIndicator = true
creatorsCollectionView.showsVerticalScrollIndicator = false
}
required init?(coder aDecoder : NSCoder) {
super.init(coder: aDecoder)
}
func setUpCollection(creators: Creator) {
creatorsLbl.text = creators.user.username
}
}
//MARK: - Extensions
extension CreatorsTableViewCell : UICollectionViewDelegate , UICollectionViewDataSource , UICollectionViewDelegateFlowLayout{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return creators.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = creatorsCollectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell",
for: indexPath) as! CollectionViewCell
// cell.setUpCollectionViewCell((nft?.creator.profileImgURL[indexPath.row])!)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated:true)
}
}
//MARK: - Protocols
protocol CreatorsTableViewCellDelegate: AnyObject {
func didSelectPhoto(index: Int)
}
CollectionViewController :
import UIKit
private let reuseIdentifier = "Cell"
class CollectionViewController: UICollectionViewController , UICollectionViewDelegateFlowLayout {
let creatorsCellId = "creatorsCellId"
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
// self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
func setupCollectionView() {
collectionView?.showsHorizontalScrollIndicator = false
collectionView?.backgroundColor = .lightGray
let nib = UINib(nibName: "CollectionViewCell", bundle: nil)
collectionView?.register(nib, forCellWithReuseIdentifier: creatorsCellId)
collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "cellId")
}
}
}
extension CollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 7
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: creatorsCellId, for: indexPath) as! CollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 180, height: view.frame.height-60)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10)
}
}
CollcetionViewCell:
import UIKit
class CollectionViewCell: UICollectionViewCell {
//MARK: - IBProperties
#IBOutlet var creatorsImg: UIImageView!{
didSet {
creatorsImg.contentMode = .scaleAspectFit
}
}
//MARK: - Properties
var nft : Nft?
//MARK: - Life Cyrcle
override func awakeFromNib() {
super.awakeFromNib()
}
func setUpCollectionViewCell(_ nft: Nft) {
let imgUrl = (nft.creator.profileImgURL)
print(imgUrl)
creatorsImg.downloaded(from: imgUrl)
// creatorsImg.image = UIImage(named: (nft.creator.profileImgURL))
creatorsImg.layoutIfNeeded()
creatorsImg.layer.cornerRadius = creatorsImg.frame.height / 2
}
}
the results is this :
I am trying to change a text label in a UICollectionViewCell, but for some reason it just will not update. Any help would be aprreciated. Thanks!
You can use didSet to make your code a bit reactive.
class CollectionViewCell : UICollectionViewCell{
var text = "" {
didSet{
self.label.text = text
}
}
private var label : UILabel = {
let label = UILabel()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CollectionViewClass : UIViewController,UICollectionViewDelegate,UICollectionViewDataSource{
var collectionView = UICollectionView()
let id = "cell ID"
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: self.view.frame)
collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: id)
collectionView.dataSource = self
collectionView.delegate = self
self.view.addSubview(collectionView)
collectionView.backgroundColor = .white
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: id, for: indexPath) as! CollectionViewCell
cell.text = "some text"
cell.backgroundColor = .red
return cell
}
}
In the above code, cell's text value is given everytime it's loaded. Whenever text is set, didset is called which updates the label value.
I have decided start a project with no storyboard for the first time and at the moment I am stuck trying to figuring out how to achieve a proper dynamic cell in my CollectionViewController. Reading some of the solutions here in Stackoverflow I got the point in using a layout.estimatedItemSize but it somehow stops the bouncing effect from the collection view and also in my second cell which is a horizontal scroll view will not work after this implementation.
Here is my code(UICollectionViewController):
class InfoEmpaVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
fileprivate let cell1 = "cell1"
fileprivate let cell2 = "cell2"
fileprivate let cellID = "cellID"
fileprivate let headerID = "headerID"
fileprivate let padding: CGFloat = 10
//
//
//GET THE DATA FROM:
var empanada: Empanadas!
struct Cells {
static let empanadaStepsCell = "EmpanadaStepsCell"
}
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionViewLayout()
setupCollectionView()
}
//CHANGE COLOR OF STATUS BAR
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
fileprivate func setupCollectionView() {
collectionView.backgroundColor = UIColor(named: "ColorBackground")
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(InfoHeaderVC.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerID)
//FirstCELL
collectionView.register(EmpaIngredientsListCell.self, forCellWithReuseIdentifier: cell1)
//SecondCELL
collectionView.register(EmpaStepsCell.self, forCellWithReuseIdentifier: cellID)
}
fileprivate func setupCollectionViewLayout() {
if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
layout.sectionInset = .init(top: padding, left: padding, bottom: padding, right: padding)
layout.estimatedItemSize = CGSize(width: view.frame.width, height: 50)
}
}
var headerView: InfoHeaderVC!
//HEADER COLLECTION VIEW
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerID, for: indexPath) as? InfoHeaderVC
headerView.empaImageView.image = UIImage(named: empanada.image)
headerView.empaTitleLabel.text = empanada.name
headerView.empaDescriptionLabel.text = empanada.info
headerView.buttonX.addTarget(self, action: #selector(dismissVC), for: .touchUpInside)
headerView.buttonAddFavorite.addTarget(self, action: #selector(addButtonTapped), for: .touchUpInside)
return headerView!
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .init(width: view.frame.width, height: 350)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cell1, for: indexPath)
guard let cellOne = cell as? EmpaIngredientsListCell else {
fatalError("Wrong cell type for section 0. Expected CellTypeOne")
}
//INGREDIENT LIST
cellOne.empaIngredientList.ingredientList.append(contentsOf: empanada.ingredients)
cellOne.empaIngredientList.configure()
return cellOne
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) as! EmpaStepsCell
cell.pasos.append(contentsOf: empanada.pasos)
cell.titleHeaderLabel.text = "Step by Step"
cell.configure()
print (cell.pasos.count)
return cell
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item == 0 {
return .init(width: view.frame.width - 2 * padding, height: 300)
} else {
return .init(width: view.frame.width - 2 * padding, height: 300)
}
}
//OBJC FUNC
#objc func dismissVC() {
dismiss(animated: true)
}
//SAVE DATA
#objc func addButtonTapped() {
configureSaveToFavorites(empanada: empanada!)
}
}
Cell 1:
import UIKit
import SnapKit
class EmpaIngredientsListCell: UICollectionViewCell {
let empaIngredientList = EmpaIngredientsContainerView()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
print(intrinsicContentSize)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var frame = layoutAttributes.frame
frame.size.height = ceil(size.height)
layoutAttributes.frame = frame
return layoutAttributes
}
func setupUI() {
//contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(empaIngredientList)
empaIngredientList.snp.makeConstraints { (make) in
make.top.bottom.left.right.equalTo(self.contentView)
make.edges.equalTo(self.safeAreaLayoutGuide)
}
}
}
I have a CollectionView in TableViewController cell the tableViewcontains 3 sections each section is a different category. How I can pass different data in each section. Like Netflix app
In UICollectionViewController file
import UIKit
import Alamofire
class HomeViewController: UITableViewController, MovieDataDelegate {
let tableViewHeaderIdentifier = "FeaturedTableHeader"
var homePresenter : HomePresenter!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let headerNib = UINib(nibName: "FeaturedUITableViewHeaderView", bundle: nil)
tableView.register(headerNib, forHeaderFooterViewReuseIdentifier: tableViewHeaderIdentifier)
homePresenter = HomePresenter()
homePresenter.delegate = self
homePresenter.fetchData()
homePresenter.fetchImageData()
}
func arrayOfMoviesNames(names: [String]) {
//print(homePresenter.homeData.moviesNamesArray)
tableView.reloadData()
}
func arrayOfPosters(path: [String]) {
//print(homePresenter.homeData.posterPathsArray)
}
//MARK: - Home UI table
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "featuredCell", for: indexPath) as! FeaturedTableViewCell
cell.homeCollectionView.dataSource = self
return cell
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: tableViewHeaderIdentifier) as! FeaturedUITableViewHeaderView
switch section {
case 0:
headerView.isHidden = true
case 1:
headerView.catagoryLabel.text = "Popular Movies"
case 2:
headerView.catagoryLabel.text = "Celebs"
default:
headerView.catagoryLabel.text = "" as String
}
return headerView
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
if section == 0 {
return 0
}
return 35
}
override func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
return 12
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let tableViewCell = cell as? FeaturedTableViewCell else { return }
tableViewCell.setCollectionViewDataSourceDelegate(self, forRow: indexPath.row)
}
}
//MARK: - collectionView
extension HomeViewController : UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return homePresenter.homeData.moviesNamesArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "homeCollectionCell", for: indexPath) as! HomeCollectionViewCell
cell.posterImage.image = UIImage(named: "Aladdin")
cell.movieName.text = "coco"
return cell
}
}
In UITableViewCell file
import UIKit
class FeaturedTableViewCell: UITableViewCell {
#IBOutlet weak var homeCollectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
let collectionNib = UINib(nibName: "HomeCollectionViewCell", bundle: nil)
homeCollectionView.register(collectionNib, forCellWithReuseIdentifier: "homeCollectionCell")
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension FeaturedTableViewCell {
func setCollectionViewDataSourceDelegate<D: UICollectionViewDataSource & UICollectionViewDelegate>(_ dataSourceDelegate: D, forRow row: Int) {
homeCollectionView.delegate = dataSourceDelegate
homeCollectionView.dataSource = dataSourceDelegate
homeCollectionView.tag = row
homeCollectionView.reloadData()
}
}
screenshot of app
I want to show diffrent image and text for each section of tabelview
Create a struct Movie and create an array of tuple in the view controller. In tuple add a category title and an array of related movies. In table view data source methods use the main array and in collection view data source methods use the movies array from the corresponding tuple. Set the current tuple/section index as the collectionView tag. And get the appropriate movies array in the collection view data source methods.
//HomeViewController
class HomeViewController: UITableViewController {
struct Movie {
var name: String
var image: UIImage?
//other details
}
var movieDetails:[(title:String, movies:[Movie])] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FeaturedCell.self, forCellReuseIdentifier: "FeaturedCell")
movieDetails = [(title: "MoviesDB", movies: [Movie(name: "a", image: UIImage(named: "a")),
Movie(name: "b", image: UIImage(named: "b")),
Movie(name: "c", image: UIImage(named: "c"))]),
(title: "Popular Movies", movies: [Movie(name: "d", image: UIImage(named: "d")),
Movie(name: "e", image: UIImage(named: "e")),
Movie(name: "f", image: UIImage(named: "f"))]),
(title: "Celebs", movies: [Movie(name: "g", image: UIImage(named: "g")),
Movie(name: "h", image: UIImage(named: "h")),
Movie(name: "i", image: UIImage(named: "i"))])]
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return movieDetails.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return movieDetails[section].title
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeaturedCell") as? FeaturedCell ?? FeaturedCell(style: .default, reuseIdentifier: "FeaturedCell")
cell.collectionView.tag = indexPath.section
cell.collectionView.delegate = self
cell.collectionView.dataSource = self
return cell
}
}
extension HomeViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return movieDetails[collectionView.tag].movies.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "HomeCell", for: indexPath) as? HomeCell ?? HomeCell()
let movie = movieDetails[collectionView.tag].movies[indexPath.item]
cell.posterImage.image = movie.image
cell.movieName.text = movie.name
return cell
}
}
//FeaturedCell
class FeaturedCell: UITableViewCell {
var collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .horizontal
}
collectionView.register(HomeCell.self, forCellWithReuseIdentifier: "HomeCell")
collectionView.translatesAutoresizingMaskIntoConstraints = false
addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
collectionView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
}
}
//HomeCell
class HomeCell: UICollectionViewCell {
let posterImage = UIImageView()
let movieName = UILabel()
//...
}
Use both UITableView data source and UICollectionView data source methods.
Set 3 sections in UITableView with only one cell for every section.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Your cell with UICollectionView inside
let cell = tableView.dequeueReusableCell(withIdentifier: "YOUR_TABLE_VIEW_CELL_IDENTIFIER", for: indexPath)
// Set UICollectionViewDataSource, also add data source methods in your class, look at next code block
cell.collectionView.dataSource = self
return cell
}
// Header for each section,
func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? {
if (section == 0) {
return nil
} else if (section == 1) {
return "Popular movies"
} else if (section == 2) {
return "Celebs"
}
}
// UICollectionViewDataSource methods
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 // only one section for each UICOllectionView
}
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
// return here number of items in each UICollectionView
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YOUR_COLLECTION_CELL_IDENTIFIER", for: indexPath)
// set real data for each UICollectionCell here
cell.label = "COCO"
return cell
}
If you want to
I made 4 identical cells with subviews by using UITableViewCell subclass 'FruitTableViewCell' class.
FruitTableViewCell.swift
class FruitTableViewCell: UITableViewCell, UITextFieldDelegate {
var fruitsTextField = UITextField()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(fruitsTextField)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
fruitsTextField.frame = CGRect(x: 100, y: 7.5, width: 50, height: 30)
fruitsTextField.backgroundColor = UIColor.darkGray
fruitsTextField.delegate = self
}
}
TableViewController.swift
class TableViewController: UITableViewController, UITextFieldDelegate {
let fruitsComponents: [String] = ["Apple", "Banana", "Grape", "Pear"]
let cellReuseidentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
}
override func numberOfSections(in tableView: UITableView) -> Int {
sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruitsComponents.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
}
It works well.
But in fact, I want to add different accessories(or subviews) for each cell. Row 0 for UITextField, Row 1 for UILabel, Row 2 for Stepper, Row 3 for UILabel, ... and so on.
So I made the other UITableViewCell subclass 'AnotherFruitTableViewCell' class to use.
And I tried by using 'if' statement.
revised TableViewController.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! FruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseidentifier, for: indexPath) as! AnotherFruitTableViewCell
cell.textLabel?.text = fruitsComponents[indexPath.row]
return cell
}
But the message 'could not cast value of type' poped up.
Because of this code, I think.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(FruitTableViewCell.self, forCellReuseIdentifier: cellReuseidentifier)
}
And fundamentally, I think 'if' statement is not a good way to add different accessories for each cells.
How can I add different accessories(or subviews) for each cell?
You registered your FruitTableViewCell but not registered AnotherFruitTableViewCell