didSelectItem not being called - ios

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
}

Related

How can I fix the error that didSelectItemAt function for UICollectionView showing wrong item (only showing first item)?

I am trying to build an app, and there is a view which shows list of soccer leagues in collection view. I've setup Firestore database to store the require data and use the following codes to retrieve them into my app.
enum LeagueListType {
case leagueList(viewModels: [LeagueCellViewModel])
}
class LeaguesViewController: UIViewController {
private var db = Firestore.firestore()
private var leagues = [LeaguesInfo]()
private var sections = [LeagueListType]()
private var collectionView: UICollectionView = UICollectionView(
frame: .zero,
collectionViewLayout: UICollectionViewCompositionalLayout { sectionIndex, _ -> NSCollectionLayoutSection? in
return LeaguesViewController.createSectionLayout()
})
override func viewDidLoad() {
super.viewDidLoad()
title = "Leagues"
view.backgroundColor = .systemBackground
configureCollectionView()
fetchData()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView.frame = view.bounds
}
private func configureCollectionView() {
view.addSubview(collectionView)
collectionView.register(UICollectionViewCell.self,
forCellWithReuseIdentifier: "cell")
collectionView.register(LeagueListCollectionViewCell.self,
forCellWithReuseIdentifier: LeagueListCollectionViewCell.identifier)
collectionView.dataSource = self
collectionView.delegate = self
collectionView.backgroundColor = .systemBackground
}
private func configureModels(leagues: [LeaguesInfo]) {
self.leagues = leagues
// Configure Models
sections.append(.leagueList(viewModels: leagues.compactMap({
return LeagueCellViewModel(id: $0.id,
name: $0.name,
logo: URL(string: $0.logo))
})))
collectionView.reloadData()
}
private func fetchData() {
db.collection("leagues").addSnapshotListener { (snapshot, error) in
DispatchQueue.main.async {
guard let documents = snapshot?.documents else {
print("No league data")
return
}
self.leagues = documents.compactMap { (documentSnapshot) -> LeaguesInfo in
let data = documentSnapshot.data()
let id = data["id"] as? String ?? ""
let name = data["name"] as? String ?? ""
let logo = data["logo"] as? String ?? ""
let leaguess = LeaguesInfo(id: id, name: name, logo: logo)
self.configureModels(leagues: [leaguess])
self.collectionView.reloadData()
return leaguess
}
}
}
}
}
Here is cellForItemAt part.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let type = sections[indexPath.section]
switch type {
case .leagueList(let viewModels):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LeagueListCollectionViewCell.identifier,
for: indexPath) as? LeagueListCollectionViewCell else {
return UICollectionViewCell()
}
let viewModel = viewModels[indexPath.row]
cell.configure(with: viewModel)
return cell
}
}
And I use the following codes for didSelectItemAt function in extension.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
let league = leagues[indexPath.row]
let vc = LeagueViewController(league: league)
vc.title = league.name
vc.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(vc, animated: true)
}
The problem is that didSelectItemAt always returning wrong item. It's only showing viewcontroller for the first item of the leagues no matter which item I select.
Could you please enlighten me how to fix it?

Questions about removing ios photo asset in Swift

This source code is a photo app like the iPhone's photo app.
When you launch the app, each Asset that is a CollectionViewCell is shown.
What I would like to ask is the ability to select and delete an image asset. If you look at the iPhone photo app, you can press the select button to select photos and delete and share selected photos. You can choose as many photos as you want rather than just one. I have implemented #IBAction selectButtonPressed.
class PhotoCollectionViewController: UICollectionViewController {
#IBOutlet weak var sortButton: UIBarButtonItem!
#IBOutlet weak var selectButton: UIBarButtonItem!
#IBOutlet weak var actionButton: UIBarButtonItem!
#IBOutlet weak var trashButton: UIBarButtonItem!
// MARK:- Properties
var fetchResult: PHFetchResult<PHAsset>? {
didSet {
OperationQueue.main.addOperation {
self.collectionView?.reloadSections(IndexSet(0...0))
}
}
}
var assetCollection: PHAssetCollection?
// MARK:- Privates
private let cellReuseIdentifier: String = "photoCell"
private lazy var cachingImageManager: PHCachingImageManager = {
return PHCachingImageManager()
}()
// MARK:- Life Cycle
deinit {
PHPhotoLibrary.shared().unregisterChangeObserver(self)
}
#IBAction func sortButtonPressed(_ sender: UIBarButtonItem) {
let fetchOptions: PHFetchOptions = PHFetchOptions()
if (self.sortButton.title == "In the past") {
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "modificationDate",
ascending: false)]
self.fetchResult = PHAsset.fetchAssets(in: assetCollection!, options: fetchOptions )
self.sortButton.title = "The latest"
} else if (self.sortButton.title == "The latest") {
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate",
ascending: true)]
self.fetchResult = PHAsset.fetchAssets(in: assetCollection!, options: fetchOptions )
self.sortButton.title = "In the past"
}
}
#IBAction func seletButtonPressed(_ sender: Any) {
if (self.sortButton.isEnabled == true) {
self.sortButton.isEnabled = false
self.actionButton.isEnabled = true
self.trashButton.isEnabled = true
} else if (self.sortButton.isEnabled == false) {
self.sortButton.isEnabled = true
self.actionButton.isEnabled = false
self.trashButton.isEnabled = false
}
PHPhotoLibrary.shared().performChanges({
//Delete Photo
PHAssetChangeRequest.deleteAssets(self.fetchResult!)
},
completionHandler: {(success, error)in
NSLog("\nDeleted Image -> %#", (success ? "Success":"Error!"))
if(success){
}else{
print("Error: \(error)")
}
})
}
}
extension PhotoCollectionViewController {
private func configureCell(_ cell: PhotoCollectionViewCell,
collectionView: UICollectionView,
indexPath: IndexPath) {
guard let asset: PHAsset = self.fetchResult?.object(at: indexPath.item) else { return }
let manager: PHCachingImageManager = self.cachingImageManager
let handler: (UIImage?, [AnyHashable:Any]?) -> Void = { image, _ in
let cellAtIndex: UICollectionViewCell? = collectionView.cellForItem(at: indexPath)
guard let cell: PhotoCollectionViewCell = cellAtIndex as? PhotoCollectionViewCell
else { return }
cell.imageView.image = image
}
manager.requestImage(for: asset,
targetSize: CGSize(width: 100, height: 100),
contentMode: PHImageContentMode.aspectFill,
options: nil,
resultHandler: handler)
}
}
// MARK:- UICollectionViewDataSource
extension PhotoCollectionViewController {
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
return self.fetchResult?.count ?? 0
}
}
extension PhotoCollectionViewController {
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: PhotoCollectionViewCell
cell = collectionView.dequeueReusableCell(withReuseIdentifier: self.cellReuseIdentifier,
for: indexPath) as! PhotoCollectionViewCell
return cell
}
override func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath) {
guard let cell: PhotoCollectionViewCell = cell as? PhotoCollectionViewCell else {
return
}
self.configureCell(cell, collectionView: collectionView, indexPath: indexPath)
}
}
// MARK:- UICollectionViewDelegateFlowLayout
extension PhotoCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
guard let flowLayout: UICollectionViewFlowLayout =
self.collectionViewLayout as? UICollectionViewFlowLayout else { return CGSize.zero}
let numberOfCellsInRow: CGFloat = 4
let viewSize: CGSize = self.view.frame.size
let sectionInset: UIEdgeInsets = flowLayout.sectionInset
let interitemSpace: CGFloat = flowLayout.minimumInteritemSpacing * (numberOfCellsInRow - 1)
var itemWidth: CGFloat
itemWidth = viewSize.width - sectionInset.left - sectionInset.right - interitemSpace
itemWidth /= numberOfCellsInRow
let itemSize = CGSize(width: itemWidth, height: itemWidth)
return itemSize
}
}
extension PhotoCollectionViewController {
private func updateCollectionView(with changes: PHFetchResultChangeDetails<PHAsset>) {
guard let collectionView = self.collectionView else { return }
// 업데이트는 삭제, 삽입, 다시 불러오기, 이동 순으로 진행합니다
if let removed: IndexSet = changes.removedIndexes, removed.count > 0 {
collectionView.deleteItems(at: removed.map({
IndexPath(item: $0, section: 0)
}))
}
if let inserted: IndexSet = changes.insertedIndexes, inserted.count > 0 {
collectionView.insertItems(at: inserted.map({
IndexPath(item: $0, section: 0)
}))
}
if let changed: IndexSet = changes.changedIndexes, changed.count > 0 {
collectionView.reloadItems(at: changed.map({
IndexPath(item: $0, section: 0)
}))
}
changes.enumerateMoves { fromIndex, toIndex in
collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
to: IndexPath(item: toIndex, section: 0))
}
}
}
// MARK:- PHPhotoLibraryChangeObserver
extension PhotoCollectionViewController: PHPhotoLibraryChangeObserver {
private func resetCachedAssets() {
self.cachingImageManager.stopCachingImagesForAllAssets()
}
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let fetchResult: PHFetchResult<PHAsset> = self.fetchResult
else { return }
guard let changes: PHFetchResultChangeDetails<PHAsset> =
changeInstance.changeDetails(for: fetchResult)
else { return }
DispatchQueue.main.sync {
self.resetCachedAssets()
self.fetchResult = changes.fetchResultAfterChanges
if changes.hasIncrementalChanges {
self.updateCollectionView(with: changes)
} else {
self.collectionView?.reloadSections(IndexSet(0...0))
}
}
}
}
extension PhotoCollectionViewController {
// MARK:- Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
PHPhotoLibrary.shared().register(self)
self.sortButton.title = "In the past"
self.actionButton.isEnabled = false
self.trashButton.isEnabled = false
}
}
AssetCollection can be selected by clicking the select barButtonItem in the upper right corner of the screen, and I want to delete or share selected pictures.
You can perfore delete action on phAsset as below:
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
//Delete Photo
PHAssetChangeRequest.deleteAssets(delShotsAsset)
},
completionHandler: {(success, error)in
NSLog("\nDeleted Image -> %#", (success ? "Success":"Error!"))
if(success){
}else{
println("Error: \(error)")
}
})

How to add a custom view after every 2 rows in a collectionView swift

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.
}
*/
}

Error on Performing Segue with UICollectionView

When I click on any UICollectionView item, I got this error on PerformSegue line:
Thread 1: Fatal error: Index out of range
However, the compiler don't read the function and simply jumps straight to the PerformSegue, so I end up out of index. How can I fix that? When the user clicks on any UICollectionView index, I gotta replace the Indexpath in the function parameter and repeat the process.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
getCategoriaPorID(IdCategoria: indexPath.row) { (data) in
DispatchQueue.main.async {
self.dataCategoriaID = data
}
}
performSegue(withIdentifier: "segueCategorias", sender:self.dataCategoriaID[indexPath.row])
}
Complete ViewController file:
import UIKit
class ViewController: UIViewController, UICollectionViewDataSource, UITableViewDataSource, UITableViewDelegate, UICollectionViewDelegate {
#IBOutlet weak var tableViewTopSell: UITableView!
#IBOutlet var collectionView: UICollectionView!
#IBOutlet weak var collectionViewBanner: UICollectionView!
var indexPathId = Int()
var dataSource: [Content] = [Content]()
var dataBanner: [Banner] = [Banner]()
var dataTopSold: [Top10] = [Top10]()
var dataCategoriaID: [CategoriaIDItems] = [CategoriaIDItems]()
override func viewDidLoad() {
super.viewDidLoad()
//Delegate TableView
self.tableViewTopSell.delegate = self
//SetupNavBarCustom
self.navigationController?.navigationBar.CustomNavigationBar()
let logo = UIImage(named: "tag.png")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
//CallAPIData
getTopSold { (data) in
DispatchQueue.main.async {
self.dataTopSold = data
self.tableViewTopSell.reloadData()
}
}
getBanner { (data) in
DispatchQueue.main.async {
self.dataBanner = data
self.collectionViewBanner.reloadData()
}
}
getAudiobooksAPI { (data) in
DispatchQueue.main.async {
self.dataSource = data
self.collectionView.reloadData()
}
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (collectionView == self.collectionView) {
return self.dataSource.count
}else{
return self.dataBanner.count
}}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (collectionView == self.collectionView) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionViewCell
let content = self.dataSource[indexPath.item]
cell.bookLabel.text = content.descricao
cell.bookImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}else if (collectionView == self.collectionViewBanner) {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCellBanner", for: indexPath) as! CollectionViewCell
let content = self.dataBanner[indexPath.item]
cell.bannerImage.setImage(url: content.urlImagem, placeholder: "")
return cell
}
return UICollectionViewCell()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
getCategoriaPorID(IdCategoria: indexPath.row) { (data) in
DispatchQueue.main.async {
self.dataCategoriaID = data
}
}
performSegue(withIdentifier: "segueCategorias", sender:self.dataCategoriaID[indexPath.row])
}
//TableView
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataTopSold.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topSoldCell", for: indexPath) as! TableViewCell
let content = self.dataTopSold[indexPath.row]
cell.labelNomeTopSell.text = content.nome
cell.imageViewTopSell.setImage(url: content.urlImagem, placeholder: "")
cell.labelPrecoDe.text = "R$ \(content.precoDe)"
//Colocar strike em cima do Preco Antigo
let oldPrice = "R$ \(content.precoDe)"
let promotionString = oldPrice + ""
let attributedStr = NSMutableAttributedString(string: promotionString)
let crossAttr = [NSAttributedStringKey.strikethroughStyle: NSUnderlineStyle.styleSingle.rawValue]
attributedStr.addAttributes(crossAttr, range: NSMakeRange(0, oldPrice.count))
cell.labelPrecoDe.attributedText = attributedStr
//
cell.labelPrecoPor.text = "R$ 119.99"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "segueId", sender:self.dataTopSold[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueId" {
let des = segue.destination as? TelaDetalheProdutos
//.item possui uma propriedade instanciada na TelaDetalheProdutos
des?.item = (sender as? Top10)
//Segue para CollectionView Categorias
} else if segue.identifier == "segueCategorias" {
let desc = segue.destination as? TelaCategorias
desc?.item = (sender as? CategoriaIDItems)
}
}
}
//Cast UIImage Extension
extension UIImageView{
func setImage(url : String, placeholder: String, callback : (() -> Void)? = nil){
self.image = UIImage(named: "no-photo")
URLSession.shared.dataTask(with: NSURL(string: url)! as URL, completionHandler: { (data, response, error) -> Void in
guard error == nil else{
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
self.image = image
if let callback = callback{
callback()
}
})
}).resume()
}
}
Use main thread only to update UI. The problem is you are setting dataCategoriaID on main thread I assume which is inside asynchronous network call function. So you are trying to perform segue before setting dataCategoriaID and that is the reason it is empty and throwing index out of range error.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
getCategoriaPorID(IdCategoria: indexPath.row) { (data) in
self.dataCategoriaID = data
performSegue(withIdentifier: "segueCategorias", sender:self.dataCategoriaID[indexPath.row])
}
}

Select Multiple Collection View Cells and Store in Array

I'm working on an onboarding flow for my iOS App in Swift. I'd like to allow users to tap other users in a collection view and have it follow those users. I need the collection view to be able to allow multiple cells to be selected, store the cells in an array and run a function once the users taps the next button. Here's my controller code:
class FollowUsers: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var tableData: [SwiftyJSON.JSON] = []
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var loadingView: UIView!
private var selectedUsers: [SwiftyJSON.JSON] = []
override func viewDidLoad() {
super.viewDidLoad()
self.getCommunities()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getUsers() {
Alamofire.request(.GET, "url", parameters: parameters)
.responseJSON {response in
if let json = response.result.value {
let jsonObj = SwiftyJSON.JSON(json)
if let data = jsonObj.arrayValue as [SwiftyJSON.JSON]? {
self.tableData = data
self.collectionView.reloadData()
self.loadingView.hidden = true
}
}
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.tableData.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
if let userName = rowData["name"].string {
cell.userName.text = userName
}
if let userAvatar = rowData["background"].string {
let url = NSURL(string: userAvatar)
cell.userAvatar.clipsToBounds = true
cell.userAvatar.contentMode = .ScaleAspectFill
cell.userAvatar.hnk_setImageFromURL(url!)
}
cell.backgroundColor = UIColor.whiteColor()
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell: UserViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("userCell", forIndexPath: indexPath) as! UserViewCell
let rowData = tableData[indexPath.row]
let userName = rowData["name"].string
let userId = rowData["id"].int
selectedUsers.append(rowData[indexPath.row])
print("Cell \(userId) \(userName) selected")
}
}
override func viewDidLoad() {
super.viewDidLoad()
collection.dataSource = self
collection.delegate = self
collection.allowsMultipleSelection = true
self.getCommunities()
}
You should be able to make multiple selections with this.

Resources