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.
Related
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.
There is a dynamic set of data i have to organise as set of swiped tables. Now I'm using UIPageViewController for this task, but it has some problems with dynamically uploaded data from net - if we swiping pages too fast, we can overtake data loading and program can crashes. Now, to solve this problems, i'm uploading data in advance of 5 pages, but i think it's bad solution and i hope there is a right solving for this task.
I found another idea - using UICollectionView for this task, but i'm not sure, that i can use tables as UICollectionViewCell in this method and i'm not sure, that this decision is correct.
What can you recommend in this situation
here is my approach to such case
first I would have my ViewController containing the collectionView as below -make sure to add your own constraints however you would like-
import UIKit
class ViewController: UIViewController {
// IBOutlets
#IBOutlet var collectionView: UICollectionView!
// MARK: Lifecycle Methods
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
}
// MARK: Private Methods
private func setupCollectionView(){
collectionView.delegate = self
collectionView.dataSource = self
collectionView.separatorStyle = .none
collectionView?.register(UINib(nibName: /* Your cell NibName */, bundle: nil), forCellWithReuseIdentifier: /* Your cell Id*/)
collectionView?.isPagingEnabled = true
}
// MARK: UICollectionView Methods
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return zekr?.subNodes?.count ?? 0
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = zekrCollectionView.dequeueReusableCell(withReuseIdentifier: /* Your cell Id */, for: indexPath) as! /* Your cell Type */
cell.cellData = /* your list that corresponds with the list that table will take */
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// this is to make each cell fill the whole page
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
then you will add tableview inside each collectionViewCell and for sure to fulfill the delegate and datasource for the tableView inside the collectionViewCell as below
import UIKit
class CollectionViewCell: UICollectionViewCell, UITableViewDataSource, UITableViewDelegate {
// MARK: IBOutlets
#IBOutlet weak var pageTable: UITableView!
var cellData: [/*Your List*/]?
// MARK: Properties
var cellData: /*Your List*/?{
didSet{
if let value = cellData {
/* reload your table */
}
}
}
// MARK: Life Cycle Methods
override func awakeFromNib() {
super.awakeFromNib()
setupPageTable()
}
private func setupPageTable(){
pageTable.delegate = self
pageTable.dataSource = self
pageTable.register(UINib(nibName: /* TableView Cell NibName */, bundle: nil), forCellReuseIdentifier: /* CellId */)
}
// MARK: UITableViewDelegate
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: /* CellId */) as! /* Cell Type */
cell.cellDataModel = cellData?[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell: UITableViewCell = cellData?[indexPath.row]
return cell.contentView.frame.size.height
}
}
and finally your tableViewCell will remain as the same however you done it initially passing to it the cellDataModel
in case the horizontal scrolling did't work for the collectionView you can google a solution depending on your swift version
the result you will be having collectionView on your home or whatever VC you are on with horizontal scrolling each cell containing tableView with it's cells with vertical scrolling acting as viewPager in android
I am Trying to Create a Table View which is
Expandable
It's Childview has a UICollectionView
UICollectionView is Dynamically rendered using API
onTouch Event of UICollectionView Item should be taken care of
Tried couple of samples didn't work
import UIKit
extension String{
func print(){
Swift.print(self)
}
func log(){
Swift.print(self)
}
}
class CustomVCViewController: UIViewController, UITableViewDataSource, UITableViewDelegate , UICollectionViewDataSource, UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ccell", for: indexPath) as! CustomCollectionViewCell
cell.backgroundColor = UIColor.blue
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
"Selection of Collection View item at \(indexPath.row)".print()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
"Selection of Table View item at \(indexPath.row)".print()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath) as! CustomTableViewCell
cell.innerCollectionView.dataSource = self
cell.innerCollectionView.delegate = self
cell.backgroundColor = UIColor.red
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
}
}
This is for the table view cell
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var innerCollectionView: 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
}
}
According to my understanding you want the tableviewcell to be expandable as collection view so Im adding the code for it , the thing is collectionview is not invalidating its first internsic content size accordingly
/// This CollectionView class is used to invalidate collectionview's default implementation of inrinsic content size
class DynamicCollectionView: UICollectionView {
override func layoutSubviews() {
super.layoutSubviews()
if !__CGSizeEqualToSize(bounds.size, self.intrinsicContentSize) {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
return collectionViewLayout.collectionViewContentSize
}
}
SO add this class to your collectionview and provide flowlayout to your collectionviewcells. also!!! return UITableViewAutomaticDimension on heightforRowAt of tableview.
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.
I know this question is already ask many times but non of the solution will work.
I have collection view in uitableview cell and i prepare a cell of table view and collection view in story board and my problem is that suppose i scroll cell 1 collection view then cell 4 collection is auto scroll to that position also. How i can handle this situation..
Please note right now i make a static design of the screen
My code
// MARK:
// MARK: table view delegate method
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if Appconstant.isIpad()
{
return 215.0
}
return 185.0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let strCell = "WorkoutCell" //+ "\(indexPath.row)"
let cell = tableView.dequeueReusableCellWithIdentifier(strCell) as? WorkoutCell
// if cell == nil
// {
// cell = work
// }
return cell!
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let cell2 = cell as! WorkoutCell
cell2.collecionWorkout.reloadData()
}
// MARK:
// MARK: collectionview delegate and data source
//1
func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
//2
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: NSIndexPath) -> CGSize {
// Adjust cell size for orientation
if Appconstant.isIpad() {
return CGSize(width: 160, height: 150)
}
return CGSize(width: 140.0, height: 130.0)
}
//3
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("WorkoutCollectionCell", forIndexPath: indexPath)
// Configure the cell
return cell
}
uitableview cell code
class WorkoutCell: UITableViewCell {
#IBOutlet weak var collecionWorkout: UICollectionView!
var flowLayout : UICollectionViewFlowLayout!
override func awakeFromNib() {
super.awakeFromNib()
self.flowLayout = UICollectionViewFlowLayout()
if Appconstant.isIpad()
{
self.flowLayout.itemSize = CGSize(width: 160, height: 150)
}
else
{
self.flowLayout.itemSize = CGSize(width: 140, height: 130)
}
self.flowLayout.scrollDirection = .Horizontal
self.flowLayout.minimumInteritemSpacing = 10.0
flowLayout.sectionInset = UIEdgeInsetsMake(0.0, 10.0, 0.0, 10.0);
collecionWorkout.collectionViewLayout = flowLayout
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Cell 1st which i scroll
cell 4th which auto scroll
There are two scenarios.
1 - Reset The UICollectionView offset position :
To Reset the collectionview position just call cell.collectionView.contentOffset = .zero in cellForRowAtIndexPath
2 - Maintain the previous scroll position :
To Maintain the previous scroll position, You'll need to have a list of offsets for each cell stored alongside your data models in the containing view controller. Then you might save the offset for a given cell in didEndDisplayingCell and setting it in cellForRowAtIndexPath instead of .zero .
UITableView works on the concept of reusability. Whenever the tableView is scrolled, the cells in the tableView will definitely be reused.
Now 2 scenarios arise with this,
Get a new cell - When the tableViewCell is reused, the collectionView inside it also will be reused and so this is the reason the contentOffset of the previous cell is retained.
Scroll to previous cell - if we've manually scrolled to a particular cell in the collectionView, we might want to retain its position when we scroll back to it.
To get this kind of functionality in tableView, we need to - manually reset the contentOffset for 1st case and need to retain the contentOffset in the 2nd one.
Another approach you can follow is using a combination of UIScrollview and UIStackView.
This is how it goes.
In storyboard, create a UIScrollView. Add a vertical UIStackView to it.
Add 4 UICollectionViews in the stackView. This number is configurable as per your requirement.
Add your controller as the dataSource and delegate of all the collectionViews.
i.e.
class ViewController: UIViewController, UICollectionViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCell
cell.label.text = "\(indexPath.row)"
return cell
}
}
class CustomCell: UICollectionViewCell {
#IBOutlet weak var label: UILabel!
}
Now, since we're using a stackView, none of the collectionViews will be reused. So, the contentOffsets of the all the collectionViews will remain intact.