Multiple UICollectionView in a UITableView are intertwined in scrolling - ios

I am totally new to iOS development, so my problem might be a noob question but I have no clue how to make my UICollectionViews scroll separately from each other. When I scroll my top UICollectionView, scroll down, my other UICollectionView is also scrolled without touching it.
The UICollectionView is configured to scroll horizontally. What I want to achieve is something like Netflix has done. With the user being able to scroll horizontally, while the list of items is shown horizontally. So far everything works, except the scrolling of one list makes the other scrolls too when they are redrawn (I guess, since I can only see it happen on lists that are not yet shown on the device.)
I hope I have described my problem properly, might you be missing some information to help me solve this I am of course happy to provide.
So my UITableController looks like this:
class MoviesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var containerView: UIView!
#IBOutlet weak var tableView: UITableView!
let categories = ["In theaters", "Popular", "Upcoming", "Top rated"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
containerView.backgroundColor = UIColor.init(hex: "232637")
tableView.backgroundColor = UIColor.init(hex: "232637")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return categories.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.rowHeight = 332;
let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as! TableViewCell
return cell
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 42;
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return categories[section]
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int){
view.tintColor = UIColor.init(hex: "232637")
let header = view as! UITableViewHeaderFooterView
header.textLabel?.textColor = UIColor.init(hex: "ff5959")
}
}
my UITableViewCell looks like this:
class TableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView: UICollectionView!
let imageNames = //Some filler data
let gameNames = //Some filler data
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
collectionView.backgroundColor = UIColor.init(hex: "232637")
collectionView.contentInset = UIEdgeInsets(top: 0,left: 0,bottom: 32,right: 0)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageNames.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! MovieCell
//cell.imageView.imageFromURL(urlString: imageNames[indexPath.row])
let image = UIImageView(image: UIImage(named: "img-logo"))
image.contentMode = .scaleAspectFit
image.frame = cell.containerView.bounds
image.layer.cornerRadius = 10;
image.clipsToBounds = true;
image.imageFromURL(urlString: imageNames[indexPath.row])
cell.containerView.addSubview(image)
cell.containerView.backgroundColor = UIColor.init(hex: "232637")
return cell
}
}
And my UICollectionViewCell looks like this:
class MovieCell: UICollectionViewCell {
#IBOutlet weak var containerView: UIView!
}
My Views look like this:

It sounds like you are failing to take into account the fact that cells (table view cells as well as collection view cells) are reused. Thus, if a table view contains a collection view and you scroll that collection view and then you scroll the table view, the table view cell is reused in a new row and the previously scrolled collection view inside it remains scrolled to where you put it previously. If that's not what you want, it's up to you to reset the scroll position of the collection view when you discover that the cell is being reused.
What gave me a clue that you might be not be understanding cell reuse is this line:
cell.containerView.addSubview(image)
That's wrong, because you're doing it even if the cell is reused, meaning that some of your cells will end up with dozens of image views overlaying one another, slowing things down and eventually perhaps causing you to run out of memory. That's not the problem you asked about, but it is a sign that you are not aware of the implications of cell reuse.

Related

Unable to scroll Collection view inside Table view cell (created in Storyboard)

There is a very similar question here, but the solution doesn't solve anything for me, mainly because my embedded collection view is already inside the table view cell's content view (I created it in storyboard).
Is there some setting that I need to check to allow my collection view to scroll? It seems that the parent table view cell is eating up all gestures.
TableViewController.swift
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.collectionView.dataSource = self
cell.collectionView.delegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath)
return cell
}
}
CustomTableViewCell.swift
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
You can find my demo project here
https://github.com/MattiaPell/CollectionView-inside-a-TableViewCell
Turns out in my case it was simple as not having User Interaction Enabled check marked for my child collection view.

UITableView not drawing cells properly on initial load - Swift

I have a table view that's in a nib file and it loads the data properly. It loads the required number of cells and fills in the data correctly. The cell is a xib file as well that includes multiple views. Each cell has a custom height. I managed to set the right height per cell using:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return (data[indexPath.row].height + 200)
}
The problem is that on initial load the cells don't get drawn properly like so:
Initial load image
The description area doesn't move where I made it move by changing its X and Y coordinates.
This gets fixed however when I scroll the table view beyond the initially loaded cells and go back to them, like so:
After scrolling
Upon loading the cell I move the description area below the image. This works for cells but not on the initially loaded ones. It only gets fixed once I scroll the broken cells out of view and go back to them. How do I fix this? How can I make it so that the cells get drawn properly on the initial load?
Edit: To clarify: The cells get loaded properly but aren't drawn correctly. I have to scroll out of the first few cells and scroll back to them for the cells to be drawn right.
Any help is appreciated. Thank you!
I can see that the image you'r trying to load is of larger dimension than that of the image container view.
You can try giving clipsToBound = True to the imageView.
This would most probably resolve your issue.
Thanks.
you want to increase UITableViewCell height based on image inside it, right?
Here is tableview datasource and delegate.
extension MyDataSource: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = self.rowHeights[indexPath.row] {
return height
} else {
return 100
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return mydatas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let mydata = mydatas[indexPath.row]
var cell: UITableViewCell
cell = tableView.dequeueReusableCell(withIdentifier: 'myCell.cellIdentify()')!
cell.contentView.backgroundColor = UIColor.clear
cell.backgroundColor = UIColor.clear
cell.tag = indexPath.row
cell.selectionStyle = .none
let image = UIImage(named: promotion)
(cell as! myCell).configureCell(downloadImage: image!)
let aspectRatio = (image?.size.height)!/(image?.size.width)!
let imageHeight = (cell.contentView.frame.width * aspectRatio) + 16
UIView.performWithoutAnimation {
self.myTableView?.beginUpdates()
self.rowHeights[indexPath.row] = imageHeight
self.myTableView?.endUpdates()
}
return cell
}
}
here is UITableViewCell custom class.
myCell
import UIKit
class myCell: UITableViewCell {
#IBOutlet weak var imageHolder: WMView!
#IBOutlet weak var actionImage: UIImageView!
#IBOutlet weak var loaderIndicator: UIActivityIndicatorView!
override func prepareForReuse() {
super.prepareForReuse()
self.loaderIndicator.isHidden = false
self.loaderIndicator.startAnimating()
self.actionImage.image = nil
}
static func cellHeight() -> CGFloat {
return 73.0
}
static func cellIdentify() ->String {
return "myCell"
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func configureCell(downloadImage: UIImage) {
self.imageHolder.layer.borderWidth = 1
self.imageHolder.layer.cornerRadius = 5
self.imageHolder.layer.borderColor = UIColor.clear.cgColor
self.imageHolder.layer.masksToBounds = true
self.imageHolder.clipsToBounds = true
//ImageHolder
self.actionImage.image = downloadImage
self.loaderIndicator.isHidden = true
}
}

Collection view cells not loading

I have a collection view nested in a table view. The collection view cells are not loading. I am not able to set the collection view delgate and dataSource in the tableViewCell init function because it unwraps as nil, so i created a load function that is called to set it when the cell loads. The issue I have now is that the dataSource/delgate functions are not being called and the table view cell is not loading. My collection view cell for now is just an image, so didn't include that. Also, I have checked that all these outlets are connected correctly, and the table view cell and collection view cell both have reuse identifies set through interface builder. Here is my code:
View Controller:
#IBOutlet weak var libraryTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.libraryTableView.dataSource = self
self.libraryTableView.delegate = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "LibraryTableViewCell", for: indexPath) as? LibraryTableViewCell else {
return LibraryTableViewCell()
}
cell.load()
return cell
}
Table View Cell:
#IBOutlet weak var carouselLabel: UILabel!
#IBOutlet weak var carousel: UICollectionView!
func load() {
layer.backgroundColor = UIColor.clear.cgColor
carouselLabel.text = "hello world"
carousel.delegate = self
carousel.dataSource = self
//carousel.reloadData()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "packCell", for: indexPath) as? PackCollectionViewCell else {
return PackCollectionViewCell()
}
return cell
}

UITableView example for Swift

I've been working with Swift and iOS for a number of months now. I am familiar with many of the ways things are done but I'm not good enough that I can just write things up without looking. I've appreciated Stack Overflow in the past for providing quick answers to get me back on track with topics I've gotten rusty on (for example, AsyncTask Android example).
iOS's UITableView is in this category for me. I've done them a few times, but I forget what the details are. I couldn't find another question on StackOverflow that just asks for a basic example and I'm looking for something shorter than many of the tutorials that are online (although this one is very good).
I am providing an answer below for my future reference and yours.
The example below is an adaptation and simplification of a longer post from We ❤ Swift. This is what it will look like:
Create a New Project
It can be just the usual Single View Application.
Add the Code
Replace the ViewController.swift code with the following:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// Data model: These strings will be the data for the table view cells
let animals: [String] = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
// cell reuse id (cells that scroll out of view can be reused)
let cellReuseIdentifier = "cell"
// don't forget to hook this up from the storyboard
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Register the table view cell class and its reuse id
self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
// (optional) include this line if you want to remove the extra empty cell divider lines
// self.tableView.tableFooterView = UIView()
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:UITableViewCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
// set the text from the data model
cell.textLabel?.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Read the in-code comments to see what is happening. The highlights are
The view controller adopts the UITableViewDelegate and UITableViewDataSource protocols.
The numberOfRowsInSection method determines how many rows there will be in the table view.
The cellForRowAtIndexPath method sets up each row.
The didSelectRowAtIndexPath method is called every time a row is tapped.
Add a Table View to the Storyboard
Drag a UITableView onto your View Controller. Use auto layout to pin the four sides.
Hook up the Outlets
Control drag from the Table View in IB to the tableView outlet in the code.
Finished
That's all. You should be able run your app now.
This answer was tested with Xcode 9 and Swift 4
Variations
Row Deletion
You only have to add a single method to the basic project above if you want to enable users to delete rows. See this basic example to learn how.
Row Spacing
If you would like to have spacing between your rows, see this supplemental example.
Custom cells
The default layout for the table view cells may not be what you need. Check out this example to help get you started making your own custom cells.
Dynamic Cell Height
Sometimes you don't want every cell to be the same height. Starting with iOS 8 it is easy to automatically set the height depending on the cell content. See this example for everything you need to get you started.
Further Reading
iOS & Swift Tutorial: UITableViewController
iOS Table View Tutorial Using Swift
For completeness sake, and for those that do not wish to use the Interface Builder, here's a way of creating the same table as in Suragch's answer entirely programatically - albeit with a different size and position.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView = UITableView()
let animals = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
let cellReuseIdentifier = "cell"
override func viewDidLoad() {
super.viewDidLoad()
tableView.frame = CGRectMake(0, 50, 320, 200)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
self.view.addSubview(tableView)
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return animals.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellReuseIdentifier) as UITableViewCell!
cell.textLabel?.text = animals[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
Make sure you have remembered to import UIKit.
In Swift 4.1 and Xcode 9.4.1
Add UITableViewDataSource, UITableViewDelegate delegated to your class.
Create table view variable and array.
In viewDidLoad create table view.
Call table view delegates
Call table view delegate functions based on your requirement.
import UIKit
// 1
class yourViewController: UIViewController , UITableViewDataSource, UITableViewDelegate {
// 2
var yourTableView:UITableView = UITableView()
let myArray = ["row 1", "row 2", "row 3", "row 4"]
override func viewDidLoad() {
super.viewDidLoad()
// 3
yourTableView.frame = CGRect(x: 10, y: 10, width: view.frame.width-20, height: view.frame.height-200)
self.view.addSubview(yourTableView)
// 4
yourTableView.dataSource = self
yourTableView.delegate = self
}
// 5
// MARK - UITableView Delegates
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell : UITableViewCell? = tableView.dequeueReusableCell(withIdentifier: "cell")
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
}
if self. myArray.count > 0 {
cell?.textLabel!.text = self. myArray[indexPath.row]
}
cell?.textLabel?.numberOfLines = 0
return cell!
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50.0
}
If you are using storyboard, no need for Step 3.
But you need to create IBOutlet for your table view before Step 4.
SWIFT 5
If you only want a tableView on your screen then you can implement UITableViewController to your ViewController and do like this to show a simple tableViewController with a label in it.
Swift file
class ToDoListViewController: UITableViewController {
let array = ["GAFDGSG","VSBFFSB","BFBFB"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
array.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath)
cell.textLabel?.text = array[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath)
}
}
And in storyboard create a UITableViewController with mentioning the identifier like this
MainStoryboard
Result
Here is the Swift 4 version.
import Foundation
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
{
var tableView: UITableView = UITableView()
let animals = ["Horse", "Cow", "Camel", "Sheep", "Goat"]
let cellReuseIdentifier = "cell"
override func viewDidLoad()
{
super.viewDidLoad()
tableView.frame = CGRect(x: 0, y: 50, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseIdentifier)
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return animals.count
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as UITableViewCell!
cell.textLabel?.text = animals[indexPath.row]
return cell
}
private func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath)
{
print("You tapped cell number \(indexPath.row).")
}
}
// UITableViewCell set Identify "Cell"
// UITableView Name is tableReport
UIViewController,UITableViewDelegate,UITableViewDataSource,UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var tableReport: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableReport.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "Report Name"
return cell;
}
}

UICollectionView setup with multiple sections in storyboard

This is the layout that I am working towards and that I have finished setting up Section 1 which is represented by the black border. I want to be able to layout my multiple cells and sections in the storyboard instead of using code, is that possible?
I am having difficulty setting up section 2, which is represented by the blue borders alongside with section 1, which have different layouts.
Is my approach to setting up the layout to be like that wrowng? If so some guidance would be appreciated!
My storyboard (UICollectionView Controller)
To support above layout, can make maintain a UITableview with various section title and UICollectionview inside each UITableviewCell to have horizontal scrolling layout.
Hope this is helpful.
i am geussing that will not work
Becuase scrolling is set to be horizontal as a collectionview
The best and easiest is to nest collectionview in tableview
That mean you have mutipls collectionviews horizontal scrolling in one viewcontroller
Edit
The code is in the comment
class TableViewCell: UITableViewCell{
#IBOutlet weak var title: UILabel!
#IBOutlet weak var collection: UICollectionView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
// super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
extension TableViewCell
{
func setCollectionViewDataSourceDelegate
<D: UICollectionView & UICollectionViewDataSource>
(_ dataSourceDelegate: D, forRow row:Int)
{
collection.delegate = dataSourceDelegate as! UICollectionViewDelegate
collection.dataSource = dataSourceDelegate
collection.reloadData()
}
}
the view controller
UIViewController,UITableViewDelegate,UITableViewDataSource,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout,UICollectionViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "maincell") as! TableViewCell
cell.title.text = names[indexPath.row]
cell.collection.tag = indexPath.row
//collection is the collection view
//tag is important so you assign the cell to the specific in the collection viewcellforitem
cell.collection.reloadData()
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView.tag == 0 {
// this is a collection view and it will be loaded in table view cell zero
return self.main1.count
}else {
return self.anotherarray.count }
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "sidecell", for: indexPath) as! Collection2
if collectionView.tag == 0 {
//belongs to tableview cell index 0
cell.title.text = self.main1[indexPath.row].Des
}
}
My approach to solve this was to create a new UIViewController and insert in the UICollectionViews that I need and then set the datasource and delegate accordingly.

Resources