UICollectionView reuse in UITableViewCell - ios

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.

Related

How to organise dynamic number of uitableview as swiping pages in swift

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

How to change the cell height after putting a collection view into a table view

I'm trying to put some images with horizontal scrolling in a tableview cell, so I use a collectionview, I put it into the cell, and everything works except the cell height. I've chosen the cell height to custom in storyboard, and changed the cell height with code too, but it still doesn't work. I'm a rookie, I can't find where the problem is, please help me, thank you guys!
1.I want to change the cell height
2.Stroyboard
class HomePageTableViewController: UITableViewController {
var ImageArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
ImageArray = ["Calendar.png","Calendar.png","Calendar.png","Calendar.png","Calendar.png"]
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ImageArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BannerCell") as! BannerTableViewCell
return cell
}
override func viewWillAppear(_ animated: Bool) {
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
}
extension HomePageTableViewController: UICollectionViewDelegate,
UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return ImageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath)
-> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerCollection", for: indexPath) as! BannerCollectionCollectionViewCell
cell.BannerImage.image = UIImage(named: ImageArray[indexPath.row])
return cell
}
}
You probably need to set the collectionviewcell size such as the following:
extension HomePageTableViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// Return the cell size
let cellSize = CGSize(width: collectionView.bounds.width, height: collectionView.bounds.width * (ratioToUse))
return cellSize
}
}
I've done something similar where I'm using a collectionview to scroll horizontally but I'm using the full width for each cell and calculating the aspectratio. However, you'll need to modify this for the size you want.
I had to do some tricks elsewhere to size the collectionview itself as well.
I would try this first then, if you're still having problems, post an image of what it looks like.
Update #2:
If you are using AutoLayout, you shouldn't need to do this but you can set the height yourself using the following:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
<#code#>
}
Update #3:
I just some testing with this. You should put a height constraint on your collectionview and that should make the tableviewcell resize appropriately. You shouldn't need "heightForRowAt" as I mentioned above. AutoLayout should handle this for you.
Try to change the content mode of the UIImageView to aspect fit. It might work.

Show/hide content in expandable collection view cell

I have a collection view with two cells. I made cells expand/collapse by select(didSelectItemAtIndexPath). Cell contains additional views which should became visible in expanded mode and hidden in collapsed. My code work well if I play with expanded cell, but if I have expanded cell and tap on collapsed the expanded cell change their size but additional view stay visible. Maybe it will be easy to understand looking on the gif.
Good scenario:
click first cell = expand it
click first cell again = collapse it and hide image view
Bag scenario:
click first cell = expand it
click second cell = expand second cell & collapse first cell but additional image view on first cell still visible
click second cell again = collapse second cell and hide their content. additional image view on first cell still visible
Here is cell code:
class ExerciseSetCell: BaseCell {
// timer
let timerButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "timer"), forState: .Normal)
button.frame = CGRectMake(0, 0, 25, 25)
button.alpha = 0
return button
}()
override func setupViews() {
super.setupViews()
backgroundColor = UIColor.whiteColor()
addSubview(timerButton)
timerButton.snp_makeConstraints { (make) in
make.width.equalTo(25)
make.height.equalTo(25)
make.bottomMargin.equalTo(-7)
make.centerX.equalTo(self)
}
}
}
Here is my collectionViewController code:
class ExerciseDetailVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var exercise: Exercise?
let exerciseSetCell = ExerciseSetCell()
private let setCell = "ExerciseSetCell"
private var expandedCellIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.registerClass(ExerciseSetCell.self, forCellWithReuseIdentifier: setCell)
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(setCell, forIndexPath: indexPath) as! ExerciseSetCell
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! ExerciseSetCell
if expandedCellIndex == indexPath.item {
UIView.animateWithDuration(0.1, animations: {
cell.weightView.alpha = 0
cell.timerButton.alpha = 0
})
expandedCellIndex = -1
} else {
UIView.animateWithDuration(0.25, animations: {
cell.weightView.alpha = 1
cell.timerButton.alpha = 1
})
expandedCellIndex = indexPath.item
}
collectionView.reloadData()
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
if expandedCellIndex == indexPath.item {
return CGSizeMake(view.frame.width, 200)
}
return CGSizeMake(view.frame.width, 60)
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAtIndex section: Int) -> CGFloat {
return 2
}

Zero horizontal space between cells in UICollectionView not working

I am new to iOS development and cannot seem to figure out why my collection view is spacing out. It almost seems as though a "margin-right" as in CSS has been applied to each cell. I have set the "Min Spacing" for cells and for lines to zero but the following is still occurring...
Screen shot of issue that is occurring
I have purposely set the collection view's background color to blue, the cell's background color to red and the label's border color to black to try to see what is what. The objective is to not see any of the 'blue' (collection view background), at all. Upon configuring the collection view, I resize the cell's width to fit the label's intrinsic width but it seems that the other cells' x-axis location stays the same... I need all the cells to come together. Any help would be greatly appreciated!
Current CollectionViewController code...
import Foundation
import UIKit
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var menu = [
("NEWS"),
("NEIGHBORHOODS"),
("CULTURE"),
("DEVELOPMENT"),
("TRANSPORTATION"),
("CITIES")
]
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Testing"
self.navigationController?.navigationBarHidden = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menu.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("UnderMenuCell", forIndexPath: indexPath) as! UnderMenuCollectionViewCell
cell.underMenuLabel.text = menu[indexPath.row]
let label_width = cell.underMenuLabel.intrinsicContentSize().width + 23
cell.underMenuLabel.layer.zPosition = 1000000
cell.underMenuLabel.layer.borderWidth = 2
cell.underMenuLabel.layer.borderColor = UIColor.blackColor().CGColor
cell.frame.size.width = label_width
return cell
}
}
You might need to set up layout attributes for the cells to specify their width:
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
attributes.frame = yourFrameHere
attributes.size = yourSizeHere
return attributes
}
Also have you tried setting a breakpoint at cell.frame.size.width = label_width to see what width is actually being set?
I ended up figuring out a way that worked perfectly:
import Foundation
import UIKit
class HomeViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var underMenuCollection: UICollectionView!
var menu = [
("NEWS"),
("NEIGHBORHOODS"),
("CULTURE"),
("DEVELOPMENT"),
("TRANSPORTATION"),
("CITIES")
]
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Testing"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(true)
let scrollToIndexPath = NSIndexPath(forItem: 2, inSection: 0)
self.underMenuCollection.scrollToItemAtIndexPath(scrollToIndexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menu.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("UnderMenuCell", forIndexPath: indexPath) as! UnderMenuCollectionViewCell
cell.underMenuLabel.text = menu[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let size: CGSize!
let testLabel = UILabel()
let labelText = menu[indexPath.row]
testLabel.text = labelText
testLabel.font = UIFont(name: "Avenir Next Condensed", size: 17)
testLabel.hidden = true
size = CGSize(width: testLabel.intrinsicContentSize().width + 13, height: self.underMenuCollection.frame.height)
return size
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
self.underMenuCollection.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
}
}
So what happens is I create a hidden label that never evens gets added to the superview, populate it with the text to calculate the intrinsic width and then set the CGSize. It works perfectly...
Picture of collection view to the left
Picture of collection view to the right
The most important part in all this was the have equal spacing between all the cells dynamically based on the width of the string.

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