I call out of another collection view cell a new collection view:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let rezeptLauncher = RezeptLauncher()
rezeptLauncher.ShowRezept()
}
Afterwards it shows the collection view, but no cells inside.
I read almost every page about this topic. Nothing is working. Any suggestions? Thank you so much in advance!
class RezeptLauncher: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width / 2.5, height: collectionView.frame.width / 2)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .red
return cell
}
var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .white
cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
return cv
}()
func ShowRezept() {
print("Rezept wird angezeigt")
if let keyWindow = UIApplication.shared.keyWindow {
keyWindow.addSubview(collectionView)
collectionView.backgroundColor = .white
collectionView.topAnchor.constraint(equalTo: keyWindow.topAnchor, constant: 40).isActive = true
collectionView.leadingAnchor.constraint(equalTo: keyWindow.leadingAnchor, constant: 40).isActive = true
collectionView.trailingAnchor.constraint(equalTo: keyWindow.trailingAnchor, constant: -40).isActive = true
collectionView.bottomAnchor.constraint(equalTo: keyWindow.bottomAnchor, constant: -40).isActive = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
}
}
}
It seems like your Rezept object is being deallocated by Swift as its lifecycle is inside your did select function.
If it is deallocated, the collectionView has no delegate anymore. Since delegates are weak entities by best practice, they will not be held strongly (thus deallocation removes it)
You should add it as an instance of the class that holds the outer collectionView, and only call the setup function inside didSelect.
Related
I have 2 collection views in parallel. One of them is a menu and the other is a big one containing vertical table views that are populated with API queried data.
When the user scrolls right, then Order History is highlighted and the next cell in the big collection view at the bottom takes over the rest of the screen and it shows its own tableview data. It's just like the implementation here.
Everything works EXCEPT when I tap on Order History in the state indicated in the attached image, the collection view at the bottom does not scroll to the 2nd cell in the big collection view. If I tap on the Current Orders cell then it does scroll to the 1st cell in the big collection view. I have no idea what's causing this problem after spending the last 48 hours on this.
This is the menu bar:
class MenuBar: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var menuCollection: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionview = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionview.translatesAutoresizingMaskIntoConstraints = false
collectionview.backgroundColor = .white
collectionview.delegate = self
collectionview.dataSource = self
collectionview.register(InvoiceCell.self, forCellWithReuseIdentifier: InvoiceCell.identifier)
return collectionview
}()
var leftIndicatorConstraint: NSLayoutConstraint?
private let menuOptions = ["Current Orders", "Order History"]
var containerViewController: ContainerViewController? // I use an instance of the parent VC to pass on the value
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(menuCollection)
menuCollection.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
menuCollection.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
menuCollection.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
menuCollection.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
menuCollection.selectItem(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .left)
setupIndicator()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupIndicator() {
let indicatorBar = UIView()
indicatorBar.backgroundColor = UIColor(hex: Constants.Colors.primary)
addSubview(indicatorBar)
indicatorBar.translatesAutoresizingMaskIntoConstraints = false
leftIndicatorConstraint = indicatorBar.leadingAnchor.constraint(equalTo: self.leadingAnchor)
leftIndicatorConstraint?.isActive = true
indicatorBar.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
indicatorBar.heightAnchor.constraint(equalToConstant: 5).isActive = true
indicatorBar.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.5).isActive = true
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
containerViewController?.scrollToMenu(menuIndex: indexPath.item) // Here I pass on the value of the menu item that's tapped
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return menuOptions.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: InvoiceCell.identifier, for: indexPath) as? InvoiceCell else { return UICollectionViewCell() }
cell.thisOption = menuOptions[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: menuCollection.frame.size.width / CGFloat(menuOptions.count), height: menuCollection.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
And this is the parent UIView Controller that contains the menu collection view as well as the big base collection view
class ContainerViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
lazy var menuBar: MenuBar = {
let menuBar = MenuBar()
menuBar.containerViewController = self
menuBar.translatesAutoresizingMaskIntoConstraints = false
return menuBar
}()
lazy var baseCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.register(PastCell.self, forCellWithReuseIdentifier: PastCell.pastCellId)
collectionView.register(CurrentCell.self, forCellWithReuseIdentifier: CurrentCell.currentCellId)
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.showsHorizontalScrollIndicator = false
collectionView.backgroundColor = .white
return collectionView
}()
var orderViewModel = OrderViewModel(order: Order(addressOne: "", addressTwo: "", city: "", postalCode: "", mpName: "", planType: 0, restaurantAddress: "", restaurantId: "", restaurantName: "", timeOfCreation: nil, currentTotal: 0.00, orderMenu: [], status: 0, itemsHTML: ""))
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
setupView()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
orderViewModel.removeListener()
}
private func setupView() {
view.addSubview(menuBar)
menuBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
menuBar.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
menuBar.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
menuBar.heightAnchor.constraint(equalToConstant: 40).isActive = true
view.addSubview(baseCollectionView)
baseCollectionView.topAnchor.constraint(equalTo: menuBar.bottomAnchor, constant: 0).isActive = true
baseCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
baseCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
baseCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
func scrollToMenu(menuIndex: Int) {
// Here I am using the passed on menu index to scroll to the appropriate cell in the big collection view.
let indexPath = IndexPath(item: menuIndex, section: 0)
baseCollectionView.scrollToItem(at: indexPath, at: .left, animated: true) // For some reason it ALWAYS SCROLL TO THE FIRST CELL }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
menuBar.leftIndicatorConstraint?.constant = scrollView.contentOffset.x / 2
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let index = Int(targetContentOffset.pointee.x / view.frame.width)
menuBar.menuCollection.selectItem(at: IndexPath(item: index, section: 0), animated: true, scrollPosition: .left)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.row {
case 0:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CurrentCell.currentCellId, for: indexPath) as? CurrentCell {
if let tabBarHeight = self.tabBarController?.tabBar.frame.size.height {
cell.adjustSize(top: menuBar.frame.height * 1.2, bottom: tabBarHeight * 0.5)
return cell
}
}
case 1:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PastCell.pastCellId, for: indexPath) as? PastCell {
cell.backgroundColor = .systemOrange
return cell
}
default:
return UICollectionViewCell()
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.size.width, height: view.frame.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
}
Turns out there's a big bug with scrollToItem that Apple hasn't fixed. In my case it's caused by isPagingEnabled. So the best solution I found was here where you disable paging before scrolling to item and then enabling it right again.
I have code like this for my collection view
let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.isPagingEnabled = true
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = .white
return cv
}()
And I want to show 5 items in this collection view horizontally while paging having 4 items on a page. It works fine when I scroll to the next page which will contain my 2nd to 5th items but then comes the bug. If I just tap on the second page it will auto-scroll to my first page. This isn't an issue when I have a total of six items and the second-page shows 3rd to 6th items. It seems to occur when the second page has fewer items to display like in my first example.
Can anybody help me with this bug?
Here is an example
class VC: UIViewController {
// add collectionView to view in viewDidLoad
view.addSubView(collectionView)
}
extension VC: UICollectionViewDelegate { }
extension VC: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellId", for: indexPath)
return cell
}
}
extension VC: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
CGSize(width: collectionView.bounds.width / 3, height: collectionView.bounds.height)
}
}
I don't understand what's happening with my UICollectionView.
I'm apologise in advance because I think the answer is very simple
I just wan't to create a UICollectionView programmatically with 4 cells but 2 of them does not appear I don't know why
Here my code:
class NewsListViewController: UIViewController {
public var newsImagesCollectionView: UICollectionView!
override func viewWillLayoutSubviews() {
if self.newsImagesCollectionView == nil {
self.createNewsImagesCollectionView()
}
}
fileprivate func createNewsImagesCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
self.newsImagesCollectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
self.newsImagesCollectionView.dataSource = self
self.newsImagesCollectionView.delegate = self
self.newsImagesCollectionView.register(NewsImagesCollectionViewCell.self, forCellWithReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier)
self.newsImagesCollectionView.backgroundColor = .yellow
self.newsImagesCollectionView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.newsImagesCollectionView)
self.newsImagesCollectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
self.newsImagesCollectionView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
self.newsImagesCollectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.newsImagesCollectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
}
}
extension NewsListViewController: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NewsImagesCollectionViewCell.reuseIdentifier, for: indexPath) as! NewsImagesCollectionViewCell
switch indexPath.row {
case 0:
cell.imageView.backgroundColor = .purple
case 1:
cell.imageView.backgroundColor = .green
case 2:
cell.imageView.backgroundColor = .orange
case 3:
cell.imageView.backgroundColor = .red
default:
cell.imageView.backgroundColor = .blue
}
cell.imageView.contentMode = .scaleAspectFit
cell.imageView.image = #imageLiteral(resourceName: "euro")
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
print(self.view.frame.width)
return CGSize(width: self.view.frame.width / 4, height: self.view.frame.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}
}
And I get this:
enter image description here
Thanks for your help
Does your collection view has the same size than your main view? because you calculating the size with the main view, is better if you use the collection view size.
return CGSize(width: collectionView.frame.width / 4, height: collectionView frame.height)
In my collection view, the cell class must have completely different appearance for different kinds of data in the array.
I am looking for a way to create multiple cells and choose a different one according to the input data, so for example :
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell1 = collectionView.dequeueReusableCell.. as! kind1
let cell2 = collectionView.dequeueReusableCell.. as! kind2
// here choose a different one for each kind of data
return cell1
}
I am trying to understand if :
How to do this and if its the right way in terms of memory ?
Is it a good design? how would you go about making a completely different layout of a single cell? ( creating views and hiding them seems like a bad approach)
You need to do something like this
First register multiple cell -
[collectionView registerNib:[UINib nibWithNibName:#"CollectionViewCellKind1" bundle:nil] forCellWithReuseIdentifier:#"CollectionViewCellKind1"]
[collectionView registerNib:[UINib nibWithNibName:#"CollectionViewCellKind2" bundle:nil] forCellWithReuseIdentifier:#"CollectionViewCellKind2"]
Now implement cellForItemAt like show -
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (data.kind == kind1) {
let cell1 = collectionView.dequeueReusableCell.. as! kind1
return cell1
} else {
let cell2 = collectionView.dequeueReusableCell.. as! kind2
return cell2
}
}
by checking the type of the data you can determine the cell.
Swift 5 example, change depending on your needs.
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
collectionView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
registerCells()
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .white
cv.dataSource = self
cv.delegate = self
cv.isPagingEnabled = true
return cv
}()
var cellId = "Cell"
var celltwoCellId = "CellTwoCell"
fileprivate func registerCells() {
collectionView.register(CellOneCell.self, forCellWithReuseIdentifier: cellId)
collectionView.register(CellTwoCell.self, forCellWithReuseIdentifier: celltwoCellId)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item == 0
{
// Cell 1
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CellOne
return cell
}
else
{
// Cell 2
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: celltwoCellId, for: indexPath) as! CellTwo
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: view.frame.width, height: view.frame.height)
}
}
I just want to mention this, for any future users who might wonder how to do that. Now, I think it's much easier to just use the indexPath.item and check whether or not it's cell 1 or cell 2. Here's an example
if indexPath.item == 0
{
// Cell 1
let cell1 = let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell1", for: indexPath) as! Cell1
return cell1
}
else
{
// Cell 2
let cell2 = let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell2", for: indexPath) as! Cell1
return cell2
}
In case you have more than 2 custom UICollectionViewCells then just add else if statements and check against its indexPath.item.
I have a UICollectionView in a UIView class because it's implemented in an other class.I set the delegate and dataSource and allowsSelection to true but didSelectItemAt method doesn't work and I can't figure out why.This is the code:
class NewRecipeViewCategoryCV: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.showsHorizontalScrollIndicator = false
cv.backgroundColor = UIColor.white
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.allowsSelection = true
return cv
}()
let newRecipeModel = NewRecipeModel()
let cellId = "cellId"
override init(frame: CGRect) {
super.init(frame: frame)
collectionView.register(NewRecipeViewCategory.self, forCellWithReuseIdentifier: cellId)
addSubview(collectionView)
collectionView.topAnchor.constraint(equalTo: topAnchor).isActive = true
collectionView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
collectionView.heightAnchor.constraint(equalTo: heightAnchor).isActive = true
collectionView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return newRecipeModel.recipeCategory.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! NewRecipeViewCategory
cell.categoryText.text = newRecipeModel.recipeCategory[indexPath.item].uppercased()
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(123)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let category: UILabel = {
let category = UILabel()
category.text = "BREAKFAST"
category.font = UIFont(name: "SourceSansPro-Regular", size: 13)
category.translatesAutoresizingMaskIntoConstraints = false
category.textColor = UIColor.black
category.sizeToFit()
return category
}()
return CGSize(width: category.frame.width+15, height: category.frame.height+12)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}