I have a UITableView with a custom cell, each of which contains a horizontally scrolling UICollectionView. When the table view cells are recycled the horizontal scroll position of the collection view is recycled with it. Should I be resetting the collection view scroll position manually when I create new cells? And if so is there a way to preserve the scroll position on a per-cell basis?
Custom UITableViewCell
class CustomCell: UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource {
var collectionView:UICollectionView!
var layout = UICollectionViewFlowLayout()
var collectionData = [UIImage]()
let kCellIdentifier = "Cell"
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layout.minimumLineSpacing = 10.0
layout.minimumInteritemSpacing = 1.0
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: layout)
collectionView.registerClass(ItemCell.self, forCellWithReuseIdentifier: kCellIdentifier)
collectionView.delegate = self
collectionView.dataSource = self
addSubview(collectionView)
}
override func layoutSubviews() {
super.layoutSubviews()
layout.itemSize = CGSize(width: 800, height: bounds.height)
collectionView.frame = bounds
}
}
extension ProjectCell: UICollectionViewDataSource {
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(kCellIdentifier, forIndexPath: indexPath) as! ItemCell
//Reset collectionView scroll position?
return cell
}
}
The best place to put the code to reset the scroll position in the UICollectionViewCell would be in the prepareForReuse method. Override it in your cell subclass and it will be called every time a previously existing cell is dequeued by dequeueReusableCellWithReuseIdentifier.
Related
I want to ask. I make a custom menubar from collectionView and I want to link and change my menubar of collection view and the data from another collectionView while swiping. Here a picture what I'm try to make
but while try to swipe to left and right my menu bar is not following, I already make a reference to capture the value. but it's still not work. here is my code
class SegmentedView: UIView {
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
return cv
}()
let knowledge = ["Pengetahuan", "Keterampilan", "Sikap"]
var selectedMenu: CGFloat?
}
extension SegmentedView: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return knowledge.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath) as! SegmentedCell
let item = knowledge[indexPath.item]
cell.nameLabel.text = item
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedMenu = CGFloat(indexPath.item) * frame.width
}
}
// This from my SegmentedViewController
class SegmentedViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var segmentedViews: SegmentedView!
let cellId = "assesmentCell"
let colors: [UIColor] = [UIColor.blue, UIColor.yellow, UIColor.green]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setupCollection()
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset.x / 3)
segmentedViews.selectedMenu = scrollView.contentOffset.x / 3
}
func setupCollection() {
if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellId)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isPagingEnabled = true
}
}
thank you.
Your selectedMenu property does not listen for an event with the didSet property observer. So when you set selectedMenu in scrollViewDidScroll, nothing happens.
It may be a solution:
var selectedMenu: CGFloat? {
didSet {
collectionView.selectItem(at: <#T##IndexPath?#>, animated: <#T##Bool#>, scrollPosition: <#T##UICollectionView.ScrollPosition#>)
}
}
But be careful with the collectionView(_:didSelectItemAt:) method of SegmentedView, it can bring some unwanted behaviors. You can use a delegate pattern to trigger the method in another class.
I have a problem with making a self sizing UICollectionView with a cell that contains UITableView with a header that has a label with dynamic height.
Can someone point me out to what I need to change in the attached sample project?
You can see on the screenshot that the table does not fit the view as the cell's height is currently manually set.
import UIKit
class ViewController: UIViewController {
static let section1 = "section1"
static let section2 = "section2"
private weak var collectionView: UICollectionView!
override func viewDidLoad() {
self.navigationItem.title = "Collection View"
super.viewDidLoad()
self.setupCollectionView()
}
private func setupCollectionView() {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.minimumInteritemSpacing = 0.0
flowLayout.minimumLineSpacing = 0.0
let collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: flowLayout)
collectionView.alwaysBounceVertical = true
collectionView.register(
SectionHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: SectionHeaderView.sectionHeaderId
)
collectionView.register(Section1.self, forCellWithReuseIdentifier: ViewController.section1)
collectionView.register(Section2.self, forCellWithReuseIdentifier: ViewController.section2)
self.view.addSubview(collectionView)
self.collectionView = collectionView
self.collectionView.translatesAutoresizingMaskIntoConstraints = false
self.collectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
self.collectionView.dataSource = self
self.collectionView.delegate = self
}
}
// MARK: - UICollectionViewDelegateFlowLayout
extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
switch indexPath.section {
case 0:
return CGSize(width: self.view.frame.width, height: 210.0)
case 1:
return CGSize(width: self.view.frame.width, height: 5 * 51.0 + 130.0) // How to enable self-sizing cells for table view inside
default:
fatalError("Unsupported section index.")
}
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: self.view.frame.width, height: 61)
}
}
// MARK: - UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: SectionHeaderView.sectionHeaderId, for: indexPath) as! SectionHeaderView
switch indexPath.section {
case 0:
header.uiLabel.text = "Section 1"
case 1:
header.uiLabel.text = "Section 2"
default:
fatalError("Unsupported section index.")
}
return header
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.section {
case 0:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewController.section1, for: indexPath) as! Section1
return cell
case 1:
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ViewController.section2, for: indexPath) as! Section2
return cell
default:
fatalError("OverviewController: Unsupported section index.")
}
}
}
To implement auto-sizing collectionView cells, you really only need a few changes.
Couple key points:
Cells must satisfy their own constraints. So, instead of calculating sizing in sizeForItemAt, make sure the cells have width and height constraints. These can be dynamic, based on content.
Add elements to the collection view cell's contentView, not to the cell itself.
For the embedded non-scrolling table view, use a subclass that sets the intrinsic content size height based on the table's contentSize. Example:
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
There is a pretty good tutorial here (not mine): https://medium.com/#andrea.toso/uicollectionviewcell-dynamic-height-swift-b099b28ddd23 that has more detailed explanations.
I implement those concepts in the project you made available, and put it up on a GitHub repo (to make it easy to see the changes): https://github.com/DonMag/ThunderCollectionView
Results:
I have a UICollectionView that has horizontal scrolling. I need to place all my collection view cells in two rows and both should scroll horizontally. The layout is as shown in the screenshot.
As shown in the above screenshot, I am going to have to build two rows which will scroll horizontally, i.e., both rows will scroll together in the same direction.
I had earlier considered using sections in the scroll view, but then the scrolling would probably be independent, and so, I am hoping to find a better solution.
I looked into this link here : A similar post
This link uses a tableview to hold multiple collection views. Even though solution seems good, I am really not sure if it could work for me, I wish to know if there is a better alternative.
I looked into other stack overflow posts regarding the same (Can’t remember which though), but they did not help much.
Now normally, we would have two columns in the collection view and we can scroll vertically. Instead of this behavior, is there any way to have two rows in a UICollectionView which can scroll horizontally and simultaneously?
Should I consider using two collection views and have some logic that binds the scrolling of both the views? (I’d rather not have two UICollectionviews just to solve this problem)
Also, I am doing this through pure code. No storyboards or xib files have been used.
Can you try the code below?
I was able to do this i guess
class CollectionViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
setUpCollectionView()
}
fileprivate func setUpCollectionView() {
let collectionFlowLayout = UICollectionViewFlowLayout()
collectionFlowLayout.scrollDirection = .horizontal
collectionFlowLayout.itemSize = CGSize(width: 145, height: 145)
collectionView.setCollectionViewLayout(collectionFlowLayout, animated: true)
collectionView.delegate = self
collectionView.dataSource = self
}
}
extension CollectionViewController: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath)
if indexPath.row % 2 == 0 {
cell.contentView.backgroundColor = UIColor.red
} else {
cell.contentView.backgroundColor = UIColor.green
}
return cell
}
}
NOTE: I have set collectionView height as 300
OUTPUT
Use 2 collectionView
let CellId1 = "CellId1"
lazy var collectionViewOne: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let width = collectionView.frame.width
let collectionViewOne = UICollectionView(frame: CGRect(x: 0, y: 100, width: width, height: 100), collectionViewLayout: layout)
collectionViewOne.showsHorizontalScrollIndicator = false
collectionViewOne.backgroundColor = .red
return collectionViewOne
}()
let CellId2 = "CellId2"
lazy var collectionViewTwo: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let collectionViewTwo = UICollectionView(frame: CGRect(x: 0, y: 250, width: width, height: 100), collectionViewLayout: layout)
collectionViewTwo.backgroundColor = .blue
return collectionViewTwo
}()
then for obtaining the numberOfItem and cellForRow:
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if collectionView == self.collectionViewOne {
return 10
} else if collectionView == self.collectionViewTwo {
return 20
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == self.collectionViewOne {
let Cell1 = collectionViewOne.dequeueReusableCell(withReuseIdentifier: CellId1, for: indexPath)
Cell1.backgroundColor = .green
return Cell1
} else if collectionView == self.collectionViewTwo {
let Cell2 = collectionViewTwo.dequeueReusableCell(withReuseIdentifier: CellId2, for: indexPath )
Cell2.backgroundColor = .purple
return Cell2
}
}
and don't forget to register the collectionView in viewDidLoad()
collectionViewOne.delegate = self
collectionViewOne.dataSource = self
collectionViewTwo.delegate = self
collectionViewTwo.dataSource = self
collectionViewOne.register(Cell1.self, forCellWithReuseIdentifier: storyCell)
collectionViewTwo.register(Cell2.self, forCellWithReuseIdentifier: cardCell)
I have a collectionview embedded within a tableview. The collectionview can have either a vertical or horizontal flowlayout. When the user makes an action, the tableviewcell height changes from 200 to full tableview height and the collection view changes flowlayout. However, the collectionView flickers since there are not enough cells loaded yet when performing the layout change from horizontal to vertical. Also, when reverting to the horizontal flowLayout, there is no transition at all. Not sure why the transition occurs when changing flowLayout from horizontal to vertical but not vertical to horiztontal. How can I avoid the flicker when changing the layout and continue to utilize Apple's transition animation when reverting to the horizontal layout?
GitHub
class ViewController: UIViewController {
// If horizontal layout, then display 3 rows (with heights 200)
// If veritcal layout, then display 1 row (with height of full tableview height)
var isHorizontal = true
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
#IBAction func barButtonTapped(_ sender: UIBarButtonItem) {
isHorizontal = !isHorizontal
sender.title = isHorizontal ? "Vertical" : "Horizontal"
tableView.reloadData()
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return isHorizontal ? 3 : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "EmbeddedTableViewCell", for: indexPath) as! EmbeddedTableViewCell
cell.setup(isHorizontal: isHorizontal)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return isHorizontal ? 200 : tableView.frame.height
}
}
class EmbeddedTableViewCell: UITableViewCell {
#IBOutlet weak var collectionView: UICollectionView! {
didSet {
collectionView.dataSource = self
collectionView.delegate = self
}
}
fileprivate let horizontalFlowLayout: UICollectionViewFlowLayout = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.scrollDirection = .horizontal
return flowLayout
}()
fileprivate let verticalFlowLayout: UICollectionViewFlowLayout = {
let flowLayout = UICollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 10
flowLayout.minimumLineSpacing = 10
flowLayout.scrollDirection = .vertical
return flowLayout
}()
func setup(isHorizontal: Bool) {
guard let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout else { return }
if isHorizontal && flowLayout.scrollDirection != .horizontal {
collectionView.setCollectionViewLayout(horizontalFlowLayout, animated: true)
} else if !isHorizontal && flowLayout.scrollDirection != .vertical {
collectionView.setCollectionViewLayout(verticalFlowLayout, animated: true)
}
}
}
extension EmbeddedTableViewCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SimpleCollectionViewCell", for: indexPath)
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 150, height: 150)
}
}
I have a collectionView in which I am trying to create a grid style layout. I'm using a flowLayout and standard UICollectionViewCell's for now, but I can't seem to get the collectionView to put multiple items on the same line, as I believe the flowLayout should do by default. Here is my collectionView code:
View Controller initialization code:
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .Horizontal
collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: layout) super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.backgroundColor = UIColor.whiteColor()
}
View Did Load:
collectionView.frame = self.view.bounds
collectionView.reloadData()
self.view.addSubview(collectionView)
Delegate and Data Source:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
return CGSize(width: 40, height: 40)
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as UICollectionViewCell
func getRandomColor() -> UIColor{
var randomRed:CGFloat = CGFloat(drand48())
var randomGreen:CGFloat = CGFloat(drand48())
var randomBlue:CGFloat = CGFloat(drand48())
return UIColor(red: randomRed, green: randomGreen, blue: randomBlue, alpha: 1.0)
}
cell.backgroundColor = getRandomColor()
return cell
}
And this is what I get:
Shouldn't the cells build sideways until the edge of the collectionView's frame is hit, and then go to a new line?
Any idea why this isn't happening?
Thanks,
Do you have number of sections as 1?
If you have more than one section it will move the cells onto the next line by default. Setting the number of sections to 1 and number of cells to multiple will solve this.
Are you sure your method that gives the size is being called? I don't see layout.delegate = self anywhere in your code. Also, you can just specify itemSize in your initialization
var flowLayout = UICollectionViewFlowLayout()
flowLayout.itemSize = CGSizeMake(40, 40)
Also, do you really want to use scrollDirection = Horizontal? if that's the case then your collection view height should be equal to item height or you need to have enough item to fill the screen and more so that it scrolls