I have a UICollectionView which lies upon a UITableViewCell. The cell is the delegate and datasource for the collection view. Everything is setup in a storyboard. The cell implements numberOfItemsInSection and cellForItemAtIndexPath methods and these methods are always called successfully. Also the cell implements methods from UICollectionViewDelegateFlowLayout protocol such as sizeForItemAtIndexPath. And these methods are never called. What may be a possible reason for that?
class SignUpFollowCategoriesCell : UITableViewCell, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout
{
#IBOutlet weak var categoryCollection: UICollectionView!
#IBOutlet weak var categoryLabel: UILabel!
#IBOutlet weak var followButton: UIButton!
func setChosen(chosen:Bool)
{
followButton.checked = chosen
}
var category:Category?
{
didSet
{
guard let confirmedFollowButton = self.followButton else
{
return
}
confirmedFollowButton.hidden = true
guard let category = self.category else
{
return
}
guard let controller = self.controller else
{
return
}
guard let sites = controller.allSites[category.id] else
{
return
}
confirmedFollowButton.hidden = sites.count == 0
}
}
weak var controller:SignUpFollowCategoriesController?
func setupWithController(control:SignUpFollowCategoriesController)
{
controller = control
guard let tmp = followButton else
{
return
}
tmp.addTarget(self, action: #selector(SignUpFollowCategoriesCell.clicked), forControlEvents: .TouchUpInside)
followButton.setStyleOptions(false, style: .ClearBackground, title: "+ Follow All")
followButton.setStyleOptions(true, style: .YellowBackground, title: "Following")
}
func clicked()
{
guard let control = controller else
{
return
}
guard let cat = category else
{
return
}
if control.categoryIsChosen(cat)
{
self.setChosen(false)
control.unfollowCategory(cat)
}else
{
self.setChosen(true)
control.followCategory(cat)
}
categoryCollection.reloadData()
}
private func updateFollowBtnStyle()
{
guard let control = controller else
{
return
}
guard let cat = category else
{
return
}
if control.categoryIsChosen(cat)
{
self.setChosen(true)
}else
{
self.setChosen(false)
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
if let tmp = self.category
{
if let sites = controller?.allSites[tmp.id]
{
return sites.count
}
}
return 0;
}
private func firstLetterCaps(str : String) -> String
{
var ret = str
ret.replaceRange(str.startIndex...str.startIndex, with: String(str[str.startIndex]).capitalizedString)
return ret
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
if let cell:SignupCategorySiteCell = collectionView.dequeueReusableCellWithReuseIdentifier("SignUpCategoryCollectionCell", forIndexPath: indexPath) as? SignupCategorySiteCell
{
cell.imageView.image = UIImage.imageWithColor(UIColor.slangBlueLight())
var slangSite : SlangSite?
if let tmpID = self.category?.id
{
let sites = controller?.allSites[tmpID]
if let site = sites![indexPath.row] as SlangSite?
{
if let url = site.details.siteProfileImageURL()
{
cell.imageView.af_setImageWithURL(url)
}
let siteName = firstLetterCaps(site.details.siteName)
cell.siteNameLabel.text = siteName
slangSite = site
}
}
if let control = controller,let site = slangSite
{
if control.siteIsChosen(site)
{
cell.followStyle()
}
else
{
cell.unfollowStyle()
}
}
cell.onCellClickedBlock = {[weak self,controller] in
if let site = slangSite, let control = controller
{
if control.siteIsChosen(site)
{
cell.unfollowStyle()
control.unfollowSite(site)
}
else
{
cell.followStyle()
control.followSite(site)
}
self?.updateFollowBtnStyle()
}
}
return cell
} else
{
collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: "DebugCell")
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("DebugCell", forIndexPath: indexPath)
cell.backgroundColor = UIColor.greenColor()
return cell
}
}
func collectionView(collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: NSIndexPath) -> CGSize
{
return CGSizeMake(92, 112)
}
func collectionView( collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
insetForSectionAt section: Int) -> UIEdgeInsets {
return UIEdgeInsetsZero
}
func collectionView( collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 100.0
}
}
Check method name according to the swift version:
public func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
=======> sizeForItemAtIndexPath
Related
I have fetched and parsed my Data from API in JSON format to FirstViewController and want to segue to the SecondViewController with the data selected at the concrete person from FirstViewController. The Problem is that I have an API with such example URL: https://www.example.com/api/?action=persons&ln=en which gives me all persons in this format:
[
{
"p_id": "4107",
"p_name": "Name1 Surname1",
"p_role": "Role1",
"general_image": "/imagedb/persons/4107/main/1.jpg"
},{
"p_id": "1978",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"general_image": "/imagedb/persons/1978/main/1.jpg"
}, {...
...}
]
I am showing all these persons in my FirstViewController in CollectionView, which is working correctly, with images, names, roles. But also I need to show my SecondViewController with the data selected in FirstVC. My API for personByID is like this URL: https://www.example.com/api/?action=person&ln=en&personId=1978 which gives me JSON Data in this format:
{
"p_id": "1978",
"p_category": "[2]",
"p_name": "Name2 Surname2",
"p_role": "Role2",
"p_short": null,
"p_text": "long text...",
"p_date_start": "1922.02.05",
"p_date_end": "",
"p_profile_image": "1",
"p_status": "1",
"p_lang": "en",
"general_image": "/imagedb/persons/1978/main/1.jpg",
"photos": [
{
"image_id": "5",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/5.jpg",
"thumbs": "/imagedb/persons/1978/thumb/5.jpg"
},
{
"image_id": "7",
"p_id": "1978",
"lang": "en",
"text": "some text...",
"general": "/imagedb/persons/1978/7.jpg",
"thumbs": "/imagedb/persons/1978/thumb/7.jpg"
}
]
}
This is my Person Struct:
struct Person {
let id: String
let name: String
let role: String
fileprivate let imageURLString: String
var imageURL: URL? {
return URL(string: "https://www.example.com\(imageURLString)")
}
}
extension Person: JSONDecodable {
init(_ decoder: JSONDecoder) throws {
self.id = try decoder.value(forKey: "p_id")
self.name = try decoder.value(forKey: "p_name")
self.role = try decoder.value(forKey: "p_role")
self.imageURLString = try decoder.value(forKey: "general_image")
}
}
This is My FIRST VC:
import UIKit
class PersonListViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var personPages: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersons()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
collectionView.deselectItem(at: selectedIndexPath, animated: animated)
}
var service = ExampleWebService()
private func loadPersons(page: Int = 0, resultsPerPage: Int = 5) {
service.persons(page: page, resultsPerPage: resultsPerPage) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else { return }
self.personPages.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personPages.last else { return nil }
let section = lastPage.pageNumber
let row = lastPage.results.count - 1
return IndexPath(row: row, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personPages.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personPages.count else {
return nil
}
guard indexPath.row < personPages[indexPath.section].results.count else {
return nil
}
let page = personPages[indexPath.section]
return page.results[indexPath.row]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let personViewController = segue.destination as? PersonViewController,
let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else {
return
}
personViewController.person = person(at: selectedIndexPath)
}
#IBAction func exitToPersonsView(segue: UIStoryboardSegue) {
}
}
extension PersonListViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personPages.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersons(page: nextPageIndex)
}
}
}
extension PersonListViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personPages.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personPages[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonListCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! PersonListCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonListViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
And this is My Second VC:
import Foundation
import UIKit
final class PersonViewController: UIViewController {
#IBOutlet weak var imagesCollectionVIew: UICollectionView!
#IBOutlet weak var personRole: UILabel!
#IBOutlet weak var customNavigationBar: UINavigationBar!
var personImagesByID: [PagedResult<Person>] = [] {
didSet {
DispatchQueue.main.async {
self.imagesCollectionVIew.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadPersonImagesByID()
}
var person: Person?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let person = person {
title = person.name
personRole.text = person.role
}
customNavigationBar.topItem?.title = title
}
var service = ExampleWebService()
private func loadPersonImagesByID(page: Int = 0, resultsperPge: Int = 5) {
service.persons(page: page, resultsPerPage: resultsperPge) { (personPage) in
guard !self.loadedPersonPageNumbers.contains(page) else {
return
}
self.personImagesByID.append(personPage)
self.updateLastIndexPath(personPage)
}
}
private(set) var lastIndexPath: IndexPath?
private func updateLastIndexPath(_ personPage: PagedResult<Person>) {
if personPage.results.isEmpty {
lastIndexPath = nil
}
else {
lastIndexPath = calculateLastIndexPath()
}
}
private func calculateLastIndexPath() -> IndexPath? {
guard let lastPage = personImagesByID.last else {
return nil
}
let section = lastPage.pageNumber
let item = lastPage.results.count - 1
return IndexPath(row: item, section: section)
}
fileprivate var loadedPersonPageNumbers: [Int] {
return personImagesByID.map { $0.pageNumber }
}
func person(at indexPath: IndexPath) -> Person? {
guard indexPath.section < personImagesByID.count else {
return nil
}
guard indexPath.item < personImagesByID[indexPath.section].results.count else {
return nil
}
let page = personImagesByID[indexPath.section]
return page.results[indexPath.item]
}
}
extension PersonViewController: UICollectionViewDelegate {
fileprivate var nextPageIndex: Int {
guard let lastPage = personImagesByID.last else {
return 0
}
return lastPage.pageNumber.advanced(by: 1)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if indexPath == lastIndexPath {
loadPersonImagesByID(page: nextPageIndex)
}
}
}
extension PersonViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.imagesCollectionVIew.frame.height - 17, height: self.imagesCollectionVIew.frame.height - 17)
}
}
extension PersonViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return personImagesByID.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return personImagesByID[section].results.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PersonViewCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImagesCollectionViewCell", for: indexPath) as! PersonViewCollectionViewCell
cell.person = person(at: indexPath)
return cell
}
}
extension PersonViewController: UINavigationBarDelegate {
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
Now my issue is that I am having a problem of how to write correctly my second struct PersonByID and when clicking on the person from First VC to show me data in Second VC from personByID URL Path.
I am implementing general collectionView in a viewController to populate data and the collection view has 2 columns and the number of rows depend on the data, and now my collectionView looks like this.
Normal collectionView:
This is what I have implemented in my app as you can see it is a normal collection view with n rows and 2 columns. But, our requirement is
Business requirement Image:
There is the custom view which is added after every 2 rows and it is static with a two labels and a button...
I don't know if it is possible and how to achieve this... And after searching for some time I learned that we can do this by using DecoratorViews and I don't know what are those and how to use them.. If anyone have any idea on how to achieve this kind of layout, please guide me..
variables:
let columnsPerRow = 2
let addAfterRows = 5
var cellToShowWithAdds = 0
Function:
func getCategoryProducts() {
var id = Int()
var categoryProductsAPI = ""
if self.brandId != nil {
id = self.brandId!
if self.selectedSubCategoryId != nil {
categoryProductsAPI = "\(API.CATEGORY_BRAND_FILTER)\(self.selectedSubCategoryId!)\(API.BRAND_ID )\(id)"
} else {
categoryProductsAPI = "\(API.CATEGORY_BRAND_FILTER)\(self.categoryId!)\(API.BRAND_ID )\(id)"
}
} else {
if self.selectedSubCategoryId != nil {
id = self.selectedSubCategoryId!
} else {
id = self.categoryId!
}
categoryProductsAPI = "\(API.CATEGORY_PRODUCTS)\(id)"
}
print(categoryProductsAPI)
self.cellToShowWithAdds = 0
self.categoryProductsData = []
self.loadingView.isHidden = false
self.loadingActivityIndicator.animate()
ServiceManager.callGetAPI(url: categoryProductsAPI, view: self, closure: { response in
self.loadingView.isHidden = true
self.loadingActivityIndicator.stopAnimating()
guard let categoryData = response?.result.value else {return}
if let categories = categoryData as? [[String : Any]] {
for product in categories {
let productName = product["product_name"] as! String
let productId = product["product_id"] as! String
let productBrand = product["product_brand"] as! String
guard let productOffPercent = product["product_sale_of"] else { return }
let productImage = product["product_image"] as! String
let productPrice = product["product_price"] as! String
let productSepcialPrice = product["product_special_price"] as! String
var newProductPrice = String()
if productSepcialPrice == "Rs.0.00" {
newProductPrice = productPrice
} else {
newProductPrice = productSepcialPrice
}
self.categoryProductsData.append(ProductDetails(productID: productId, productName: productName, productPrice: productPrice, productSpecialPrice: newProductPrice, productOff: productOffPercent, productBrand: productBrand, productImageURL: productImage))
}
let quot = (self.categoryProductsData.count/(self.columnsPerRow * self.addAfterRows))
self.cellToShowWithAdds = self.categoryProductsData.count + quot + 1
DispatchQueue.main.async {
self.categoryProductsCollection.reloadData()
}
}
}, errorAction: {
self.loadingView.isHidden = true
self.loadingActivityIndicator.stopAnimating()
}, okAction: {
self.view.setNeedsLayout()
self.viewWillAppear(true)
})
}
DataSource methods:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return cellToShowWithAdds
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row % 5 != 0 {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "productCell", for: indexPath) as! ProductDisplayCell
let productId = Int(categoryProductsData[indexPath.item].productID)
cell.tag = productId!
if categoryProductsData[indexPath.item].productImageURL != "" {
if let productImage = URL(string: categoryProductsData[indexPath.item].productImageURL) {
cell.productImage.getImageWith(imageUrl: productImage)
}
} else {
cell.productImage.image = nil
}
cell.productNameLabel.text = categoryProductsData[indexPath.item].productName
cell.sellerNameLabel.text = categoryProductsData[indexPath.item].productBrand
cell.offerPercentLabel.text = "\(categoryProductsData[indexPath.item].productOff)% Off"
if "\(categoryProductsData[indexPath.item].productOff)" == "" || "\(categoryProductsData[indexPath.item].productOff)" == "100" || "\(categoryProductsData[indexPath.item].productOff)" == "0" {
cell.offerPercentLabel.isHidden = true
} else {
cell.offerPercentLabel.isHidden = false
}
if categoryProductsData[indexPath.item].productSpecialPrice != "Rs.0.00" {
if categoryProductsData[indexPath.item].productPrice == categoryProductsData[indexPath.item].productSpecialPrice {
cell.originalPriceLable.isHidden = true
cell.offerPriceLabel.isHidden = false
} else {
cell.originalPriceLable.isHidden = false
cell.offerPriceLabel.isHidden = false
}
} else if categoryProductsData[indexPath.item].productSpecialPrice == "Rs.0.00" {
cell.originalPriceLable.isHidden = true
cell.offerPriceLabel.isHidden = true
} else {
cell.originalPriceLable.isHidden = false
cell.offerPriceLabel.isHidden = false
}
cell.originalPriceLable.attributedText = categoryProductsData[indexPath.item].productPrice.strikeThrough()
cell.offerPriceLabel.text = categoryProductsData[indexPath.item].productSpecialPrice
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "formCollectionCell", for: indexPath) as! PostRequirementCellCollectionViewCell
return cell
}
}
My Code should be explanatory. I have set some values in viewdidload to get the kind of view you require.
import UIKit
class CollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
#IBOutlet weak var collectionView : UICollectionView!
let totalProducts = 21
let columnsPerRow = 2
let addAfterRows = 2
var celltoShowWithAds = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let quot = (totalProducts/(columnsPerRow * addAfterRows))
print(quot)
celltoShowWithAds = totalProducts + quot + 1
collectionView.register(UINib(nibName: "CollectionItemCell", bundle: nil), forCellWithReuseIdentifier: "CollectionItemCell")
collectionView.register(UINib(nibName: "CollectionAdvertisementCell", bundle: nil), forCellWithReuseIdentifier: "CollectionAdvertisementCell")
collectionView.delegate = self
collectionView.dataSource = self
collectionView.reloadData()
//collectionView.backgroundColor = UIColor.blue
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return celltoShowWithAds
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.row == 0{
let myCell:CollectionAdvertisementCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionAdvertisementCell", for: indexPath) as! CollectionAdvertisementCell
return myCell as CollectionAdvertisementCell;
}else if indexPath.row % 5 == 0{
let myCell:CollectionAdvertisementCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionAdvertisementCell", for: indexPath) as! CollectionAdvertisementCell
return myCell as CollectionAdvertisementCell;
}else{
let myCell:CollectionItemCell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionItemCell", for: indexPath) as! CollectionItemCell
return myCell as CollectionItemCell;
}
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == 0{
return CGSize(width: view.frame.width, height: 0.0)
}else if indexPath.row % 5 == 0 {
return CGSize(width: view.frame.width, height: 80.0)
}else{
return CGSize(width: view.frame.width/CGFloat(columnsPerRow), height: 200.0)
}
}
//Use for interspacing
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
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
I am creating an app with Swift 4, where I make a request to the API and I want to return a result on a CollectionView.
But I get the following error, which I think is from constraints:
This block is repeated 100 times.
And the result is that he does not paint any cells. Showing an image like this:
Unless I press the top button "CHANGE AUTOLAYOUT" twice. Which is when you paint the cells of the two display modes you have, and it looks like this:
And this:
But the problem is, initially nothing is shown and should be shown. And the error that I show you in the beginning appears.
To help you a little, because I would say that the problem derives from the constrainst applied, I attach some images with the different constrainsts applied.
The initial xib, where the collectionView is, are:
The cell that is initially loaded is:
The cell once we have changed the layout is this:
I attached the code of the main class, the ViewVontroller that controls the CollectionView:
import UIKit
import RxSwift
import RxCocoa
final class SpeedRunListViewController: UIViewController {
#IBOutlet private var collectionView: UICollectionView!
#IBOutlet private var buttonChangeLayout: UIButton!
private let disposeBag = DisposeBag()
private var viewModelList: SpeedRunListViewModel?
private var numElementsByCol: CGFloat = 3
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.isNavigationBarHidden = true
setupCollectionView()
viewModelList = SpeedRunListViewModel(interactor: InteractorSpeedRunSearch())
setupRx(viewModel: viewModelList!)
viewModelList?.fetch()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.isNavigationBarHidden = true
}
private func setupCollectionView() {
registerCollectionCells()
if #available(iOS 10.0, *) {
collectionView.isPrefetchingEnabled = false
} else {
// Fallback on earlier versions
}
calculateLayoutCollectionItem()
}
private func registerCollectionCells() {
collectionView.register(UINib(nibName: SpeedRunRowCollectionViewCell.nibName, bundle: nil),
forCellWithReuseIdentifier: SpeedRunRowCollectionViewCell.reuseCellId)
collectionView.register(UINib(nibName: SpeedRunCollectionViewCell.nibName, bundle: nil),
forCellWithReuseIdentifier: SpeedRunCollectionViewCell.reuseCellId)
}
private func calculateLayoutCollectionItem() {
if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
layout.estimatedItemSize = CGSize.init(width: 2, height: 2)
}
}
private func setupRx(viewModel: SpeedRunListViewModel) {
viewModel.numElements.asObservable().subscribe(onNext: { e in
self.collectionView.reloadData()
}, onError: { error in
}, onCompleted: {
}, onDisposed: {
}).disposed(by: disposeBag)
buttonChangeLayout.rx.tap.subscribe(onNext: { void in
guard let value = self.viewModelList?.layoutRow else {
return
}
self.viewModelList?.layoutRow = !value
self.collectionView.collectionViewLayout.invalidateLayout()
self.collectionView.reloadData()
}, onError: { error in
}, onCompleted: {
}, onDisposed: {
}).disposed(by: disposeBag)
}
fileprivate func getCellId() -> String {
if let layoutRow = self.viewModelList?.layoutRow, layoutRow == true {
return SpeedRunRowCollectionViewCell.reuseCellId
}
return SpeedRunCollectionViewCell.reuseCellId
}
}
//MARK: - UICollectionViewDelegate, UICollectionViewDataSource
extension SpeedRunListViewController: UICollectionViewDelegate,
UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
guard let numElements = viewModelList?.numElements else {
return 0
}
return numElements.value
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: getCellId(), for: indexPath) as! SpeedRunCollectionViewCellBase
if let cellViewModel = viewModelList?.getCellViewModel(index: indexPath.row) {
cell.setupCell(viewModel: cellViewModel)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let speedRun = viewModelList?.getSpeedRun(index: indexPath.row) else {
return
}
let interactorDetail: InteractorSpeedRunDetail = InteractorSpeedRunDetail(speedRun: speedRun)
let viewControllerDetail: SpeedRunDetailViewController = SpeedRunDetailViewController(interactor: interactorDetail)
viewControllerDetail.URISpeedRunDetail = (speedRun.links![1].uri)!
navigationController?.pushViewController(viewControllerDetail, animated: true)
}
}
And the truth is that I do not know why that conflict of layouts occurs. But it's driving me crazy ... I can not understand how the cells are not shown initially (because data is being received). What could it be?
Thank you very much, any question you attach it to me.
[CODE UPDATED]
These is the code solution:
//MARK: - UICollectionViewDelegateFlowLayout
extension SpeedRunListViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize{
if let value = self.viewModelList?.layoutRow {
if value {
return CGSize(width: 320, height: 144)
}
else{
return CGSize(width: 96, height: 162)
}
}
return CGSize(width: 320, height: 144)
}
}
You are not setting the UICollectionViewDelegateFlowLayout in the viewController. You need to set it and then use
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
To set the sizes of your cells.
You are having the error because when you load the cells the very first time you are basically telling them that they have a size of 0 0.
My didSelectItemAt method is not being called and nothing is being printed into the console. I have user interaction turned on and I still can not get it to print out anything. I am not sure if my custom PinterestStyle Layout is causing this or if I am missing something. The ultimate goal would be to segue into a detail view controller showing the profile page of the cell selected. I will do that using prepareForSegue however I still can't even get it to print out the name of the cell when tapped.
class PagesCollectionViewController: UICollectionViewController, firebaseHelperDelegate {
var storageRef: StorageReference!{
return Storage.storage().reference()
}
var usersList = [String]()
var authService : FirebaseHelper!
var userArray : [Users] = []
var images: [UIImage] = []
var names: [String] = []
override func viewWillAppear(_ animated: Bool) {
if Global.Location != "" && Global.Location != nil
{
usersList = Global.usersListSent
print(usersList)
self.authService.ListOfUserByLocation(locationName: Global.Location, type: .ListByLocation)
}
}
override func viewDidLoad() {
self.collectionView?.allowsSelection = true
self.collectionView?.isUserInteractionEnabled = true
super.viewDidLoad()
self.authService = FirebaseHelper(viewController: self)
self.authService.delegate = self
setupCollectionViewInsets()
setupLayout()
}
private func setupCollectionViewInsets() {
collectionView!.backgroundColor = .white
collectionView!.contentInset = UIEdgeInsets(
top: 20,
left: 5,
bottom: 49,
right: 5
)
}
private func setupLayout() {
let layout: PinterestLayout = {
if let layout = collectionViewLayout as? PinterestLayout {
return layout
}
let layout = PinterestLayout()
collectionView?.collectionViewLayout = layout
return layout
}()
layout.delegate = self
layout.cellPadding = 5
layout.numberOfColumns = 2
}
func firebaseCallCompleted(data: AnyObject?, isSuccess: Bool, error: Error?, type: FirebaseCallType) {
if(type == .ListByLocation) {
if(isSuccess) {
self.userArray.removeAll()
self.images.removeAll()
self.images.removeAll()
if(data != nil) {
let dataDict = data as! NSDictionary
let keyArray = dataDict.allKeys
for i in 0 ..< keyArray.count {
var dict = NSDictionary()
dict = dataDict.object(forKey: keyArray[i]) as! NSDictionary
self.userArray.append(Users.init(data: dict))
}
}
self.collectionView?.reloadData()
}
else {
print(error?.localizedDescription)
SVProgressHUD.dismiss()
}
}
}
}
extension PagesCollectionViewController {
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return userArray.count
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(userArray[indexPath.row].name)
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: "PagesCollectionViewCell",
for: indexPath) as! PagesCollectionViewCell
cell.nameLabel.text = userArray[indexPath.row].name
if let imageOld = URL(string: userArray[indexPath.row].photoURL){
cell.photo.sd_setImage(
with: imageOld,
placeholderImage: nil,
options: [.continueInBackground, .progressiveDownload]
)
}
return cell
}
}
extension PagesCollectionViewController : PinterestLayoutDelegate {
func collectionView(collectionView: UICollectionView,
heightForImageAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
var image: UIImage?
let url = URL(string: userArray[indexPath.row].photoURL)
let data = try? Data(contentsOf: url!)
image = UIImage(data: data!)
return (image?.height(forWidth: withWidth))!
}
func collectionView(collectionView: UICollectionView,
heightForAnnotationAtIndexPath indexPath: IndexPath,
withWidth: CGFloat) -> CGFloat {
return 30
}
}
Check 2 conditions:-
Make sure you have set delegate to UICollectionView
Make sure Content in PageCollectionCell like image having no user interaction enabled. If image user interaction is enabled then didSelectItemAt will not call.
as Manish Mahajan said a quick fix would be:
in cellForItemAt func set contentView as not clickable
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.contentView.isUserInteractionEnabled = false
return cell
}
This is the code form the 'Opgeslagen Rooster' Viewcontroller
class vcSavedTimetable: UIViewController {
#IBOutlet weak var noTimetableSavedLabel: UILabel!
var timetable: Timetable!
override func viewWillAppear(_ animated: Bool) {
prepareView()
}
override func viewWillDisappear(_ animated: Bool) {
noTimetableSavedLabel.isHidden = true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSavedTimetable"{
let viewController = segue.destination as! vcTimetableShow
viewController.timetable = self.timetable
viewController.willSave = false
}
}
func prepareView(){
let savedTimetable = getTimetableFromUserDefaults()
guard (savedTimetable != nil) else {
noTimetableSavedLabel.isHidden = false
return
}
self.timetable = savedTimetable!
performSegue(withIdentifier: "showSavedTimetable", sender: nil)
}
func getTimetableFromUserDefaults()->Timetable?{
let timetableID = UserDefaults.standard.string(forKey: "savedTimetableID")
if isConnectedToNetwork(){
if let id = timetableID{
let table = Timetable(timetableID: id)
return table
}
}
let val = UserDefaults.standard.data(forKey: "savedTimetable")
if let data = val{
let table = NSKeyedUnarchiver.unarchiveObject(with: data) as! Timetable
return table
}
return nil
}
}
This is the code of the destination viewcontroller 'Rooster Bekijken'. The viewdidload() function should be executed, which doesn't happen. I tested it using breakpoints
//
// vcTimetableShowCollectionViewController.swift
// Ostrea Rooster App
//
// Created by Giel-Jan Looij on 14-12-16.
// Copyright © 2016 GJ-Computers. All rights reserved.
//
import UIKit
private let reuseIdentifier = "Cell"
class vcTimetableShow: UICollectionViewController, UICollectionViewDelegateFlowLayout {
weak var timetable: Timetable?
var willSave = false
//UIVIEWCONTROLLER
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.delegate = self
collectionView?.register( UINib(nibName: "timetableViewCell", bundle: nil), forCellWithReuseIdentifier: "lessonEntry")
NotificationCenter.default.addObserver(self, selector: #selector(self.dataDidArrive(_:)), name: .didFetchTimetable, object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//METHODES
func dataDidArrive(_ notification: NSNotification){
collectionView?.reloadData()
if let cv = self.collectionView {
cv.layoutIfNeeded()
let indexPath = IndexPath(item: 0, section: getDayOfWeek()-1)
if let attributes = cv.layoutAttributesForSupplementaryElement(ofKind: UICollectionElementKindSectionHeader, at: indexPath) {
let topOfHeader = CGPoint(x: 0, y: attributes.frame.origin.y - cv.contentInset.top)
cv.setContentOffset(topOfHeader, animated:true)
}
}
if willSave {
UserDefaults.standard.set(timetable!.timetableID, forKey: "savedTimetableID")
let encodedTimetable = NSKeyedArchiver.archivedData(withRootObject: self.timetable!)
UserDefaults.standard.set(encodedTimetable, forKey: "savedTimetable")
UserDefaults.standard.synchronize()
}
}
func getDayOfWeek()->Int {
let myCalendar = Calendar(identifier: .gregorian)
let weekDay = myCalendar.component(.weekday, from: Date())
if weekDay > 6 || weekDay < 2{
return 5
}
return weekDay-1
}
//UICOLLECTIONVIEW DATASOURCE
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return timetable!.days.count
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return timetable!.days[section+1]!.hour.count * 2
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.item % 2 == 0{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "hourIndicator", for: indexPath)
let dayLabel = cell.viewWithTag(1) as! UILabel
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
dayLabel.text = String(cellHour.hourID)
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 2
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "lessonEntry", for: indexPath) as! timetableViewCell
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
cell.cellHour = cellHour
cell.prepareCell()
return cell
}
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "dayHeaderCell", for: indexPath as IndexPath)
let dayIndex = indexPath.section + 1
let dayName = timetable!.days[dayIndex]!.dayName
let day = cell.viewWithTag(1) as! UILabel
day.text = dayName
if timetable!.days[dayIndex]!.dayID == getDayOfWeek(){
cell.backgroundColor = UIColor.lightGray
}
return cell
}
//UICOLLECTIONVIEW DELEGATE
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.item % 2 == 0{
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
var cellHeight: CGFloat = 0
for _ in cellHour.lessons{
cellHeight += 23
}
if cellHour.notice != ""{
cellHeight += 23
}
return CGSize(width: 50, height: cellHeight)
}else{
let dayIndex = indexPath.section + 1
let hourIndex = indexPath.item/2 + 1
let cellHour = timetable!.days[dayIndex]!.hour[hourIndex]!
var cellHeight: CGFloat = 0
for _ in cellHour.lessons{
cellHeight += 23
}
if cellHour.notice != ""{
cellHeight += 23
}
return CGSize(width: 226.0, height: cellHeight)
}
}
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 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
let edgeInset: CGFloat = (collectionView.frame.width - (226.0 + 50.0))/2
return UIEdgeInsets.init(top: 0, left: edgeInset, bottom: 30, right: edgeInset)
}
}
I'm trying to segue from 'Opgeslagen Rooster' to 'Rooster Bekijken', but it doesn't work. It is running in the main thread (thread 1), so that isn't the problem. It gets to prepare(for segue...), does everything. After that the view should change right? But that doesn't happen. The viewdidload method of the 'Rooster Bekijken' ViewController doesn't get called. It hangs after the last line in the pepare(for segue ....) function.
Why doesn't it change to the other viewcontroller? It does work when im accessing the 'Rooster Bekijken' viewcontroller form the other viewcontroller which segue's to it.