I'm quite new to Swift. My problem is that my UICollectionView is disappearing.
In Xcode, it shows that everything is in place, but when I launch on a simulator or a device it disappears only left with the Navigation Bar and the Tab Bar.
Does anyone know what caused this or how to solve this?
Cheers!
Here is my code:
class User: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 0
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView
header.userEmail.text = PFUser.current()!.email
header.userChosenName.text = PFUser.current()!.object(forKey: "nickname") as? String
header.profilePicture.layer.cornerRadius = header.profilePicture.frame.size.width/2
header.profilePicture.clipsToBounds = true
let query = PFUser.current()?.object(forKey: "profilePicture") as! PFFile
query.getDataInBackground { (image, error) in
if error == nil {
header.profilePicture.image = UIImage(data: image!)
}
else {
SVProgressHUD.showError(withStatus: "Something's Wrong...")
}
}
return header
}
You are using the generated class and you have the number of sections 0 and number of rows 0 you have to change those based the count you want them to show
override func viewDidLoad() {
super.viewDidLoad()
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// The number of sections you have to provide
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Number of Items in this section at least it should be 1
return 1
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "Header", for: indexPath) as! UserHeaderView
header.userEmail.text = PFUser.current()!.email
header.userChosenName.text = PFUser.current()!.object(forKey: "nickname") as? String
header.profilePicture.layer.cornerRadius = header.profilePicture.frame.size.width/2
header.profilePicture.clipsToBounds = true
let query = PFUser.current()?.object(forKey: "profilePicture") as! PFFile
query.getDataInBackground { (image, error) in
if error == nil {
header.profilePicture.image = UIImage(data: image!)
}
else {
SVProgressHUD.showError(withStatus: "Something's Wrong...")
}
}
return header
}
Edit:
You should have a cellForItemAt indexPath func else app will crash. here is an example of the function
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// this will return an empty cell where you're app will not crash
// but you have to create a cell and populate some data to the cell
return UICollectionViewCell()
}
Related
This is what i have done so far but it keeps giving me the error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindSectionFooter with identifier ProfileHeader - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'
how would i fix this?
private let reuseIdentifier = "Cell"
private let headerIdentifier = "ProfileHeader"
class UserProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: Properties
var USER_NAME: String = "Username"
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: headerIdentifier, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
self.navigationItem.title = "Profile"
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView!.register(ProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
fetchUsersData()
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 200)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
//Declare header
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! ProfileHeader
return header
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
return cell
}
// MARK - API
func fetchUsersData() {
guard let currentUser = Auth.auth().currentUser?.uid else { return }
print("Current user id is \(currentUser)")
Database.database().reference().child("Users").child(currentUser).child(USER_NAME).observeSingleEvent(of: .value) { (snapshot) in
guard let username = snapshot.value as? String else {return}
self.navigationItem.title = username
}
}
}
ViewForSupplementary is called for both header and footer view
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! ProfileHeader
return header
}
else {
fatalError("Unexpected element kind")
}
}
And if you don't want viewForSupplementaryElement to be called for footer set
extension UserProfileVC: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize.zero
}
}
Also Please remove
collectionView.register(UINib(nibName: headerIdentifier, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
This is registering a ProfileHeader as cell and in next statement you register ProfileHeader as section header also self.collectionView!.register(ProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
EDIT 2:
As OP is facing difficulty fun understanding solution, I am writing whole class below
private let reuseIdentifier = "Cell"
private let headerIdentifier = "ProfileHeader"
class UserProfileVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: Properties
var USER_NAME: String = "Username"
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(UINib(nibName: headerIdentifier, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
self.navigationItem.title = "Profile"
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
self.collectionView!.register(ProfileHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
fetchUsersData()
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return 0
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return CGSize.zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: view.frame.width, height: 200)
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
if kind == UICollectionView.elementKindSectionHeader {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath) as! ProfileHeader
return header
}
else {
fatalError("Unexpected element kind")
}
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// Configure the cell
return cell
}
// MARK - API
func fetchUsersData() {
guard let currentUser = Auth.auth().currentUser?.uid else { return }
print("Current user id is \(currentUser)")
Database.database().reference().child("Users").child(currentUser).child(USER_NAME).observeSingleEvent(of: .value) { (snapshot) in
guard let username = snapshot.value as? String else {return}
self.navigationItem.title = username
}
}
}
In my case, I am trying to create a UICollcetionView with first cell add button for add user and from second cell I need to load Image array string into another cells. Here, I am using two separate cell but I am not getting first cell into my output. Please provide me solution or another best method.
I am trying to get below output
Multiple Cell Code
// MARK: CollectionView Delegate
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let firstCell = collectionView.dequeueReusableCell(withReuseIdentifier: "addusercell", for: indexPath) as! AddParticipantsCollectionViewCell
return firstCell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "userscollectionview", for: indexPath as IndexPath) as! ParticipantsCollectionViewCell
cell.imageView?.image = self.imageArray[indexPath.row]
return cell
}
}
You need
if indexPath.item == 0 {
// first cell
}
else {
// second cell
cell.imageView?.image = self.imageArray[indexPath.item - 1]
}
Correct dataSource method ( remove it )
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 // Section should set return 1 or default is optional
}
And change numberOfItemsInSection to
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return imageArray.count + 1
}
If you have two sections in the collectionView you also need to set the number of items in each individual section:
// MARK: CollectionView Delegate
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 {
return 1 // return 1 item in cell (+ cell)
} else {
return imageArray.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if indexPath.section == 0 {
let firstCell = collectionView.dequeueReusableCell(withReuseIdentifier: "addusercell", for: indexPath) as! AddParticipantsCollectionViewCell
return firstCell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "userscollectionview", for: indexPath as IndexPath) as! ParticipantsCollectionViewCell
cell.imageView?.image = self.imageArray[indexPath.row]
return cell
}
}
I hope it's been of any help.
I am currently having an issue with UICollectionViewCell and when it gets loaded.
Here is the link to the video to see it in action
Below is my code in viewDidLoad i call
retrieveUrls { (success) in
if success {
self.filterImageLabel(handler: { (success) in
if success {
if self.spinner != nil {
self.spinner.stopAnimating()
self.spinner.isHidden = true
}
self.collectionView.reloadData()
}
})
}
}
In retrieveUrls i am parsing the Url to retrieve image URL and for filterImageLabel i set up an photoUrlArray to use with collectionView indexpath
func filterImageLabel(handler: #escaping (_ status: Bool) -> ()) {
photoUrlArray = photoUrlArray.filter {$0.label == "Large Square"}
if photoUrlArray.count > 0 {
handler(true)
}
}
Methods for CollectionView
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return photoUrlArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? PhotoCell {
cell.updateCellImage(imageUrl: self.photoUrlArray[indexPath.row].source)
return cell
}
return PhotoCell()
}
And Finally in photocell class i am setting up the image
override func prepareForReuse() {
super.prepareForReuse()
photoImg.image = nil
}
func updateCellImage(imageUrl : String) {
Alamofire.request(imageUrl).responseImage(completionHandler: { (response) in
guard let image = response.result.value else {return}
self.photoImg.image = image
})
}
I have looked at various different thread on stack-overflow. however cannot seem to resolve the issue.
Any ideas would be helpful.
Method for CollectionView is as follows:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as? PhotoCell {
// ********* ISSUE IS HERE *********
let imageurl = URL(string: self.photoUrlArray[indexPath.row].source)
cell.photoImg.af_setImage(withURL: imageurl!)
return cell
}
return PhotoCell()
}
I got my data from a url as json string. in my codes, titles variable is an array of my url images.
private let reuseIdentifier = "cell_supporters"
class SupportersCollectionViewController: UICollectionViewController {
var ids = [String]()
var titles = [String]()
#IBOutlet var collection_supporters: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
collection_supporters.delegate = self
// Register cell classes
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
getSupporters()
}
// MARK: UICollectionViewDataSource
// override func numberOfSections(in collectionView: UICollectionView) -> Int {
// // #warning Incomplete implementation, return the number of sections
// return 1
// }
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return self.titles.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! SupportersCollectionViewCell
print("Hello collectionView")
let url_img = URL(string: self.titles[indexPath.row])!
print(url_img)
cell.img_logo.af_setImage(
withURL: url_img
)
return cell
}
func getSupporters() {
RestApiManager.sharedInstance.getSupporters { (json: JSON) in
if let results = json.array {
for entry in results {
// print(entry["title"])
//self.ids.append(entry["id"].string!)
self.titles.append(entry["title"].string!)
}
print(self.titles.count)
DispatchQueue.main.async{
self.collection_supporters.reloadData()
}
}
}
}
}
in my code :
print(self.titles.count) // it shows 5
but:
print("Hello collectionView") // not show anything !
If you're using the default UICollectionViewFlowLayout class for the layout you can try implementing the delegate method that returns the size of each item in the collectionView. The UICollectionView doesn't call the cellForItemAt dataSource method at all if it doesn't have the size information or if they're zero.
Try adding this:
extension SupportersCollectionViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 40, height: 40) // Return any non-zero size here
}
}
Dont register cell classes. Delete following line from viewDidLoad function :
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
Add this line in your ViewDidLoad() method:
self.collectionView!.delegate = self
Change it to this:
private let reuseIdentifier = "cell_supporters"
class SupportersCollectionViewController: UICollectionViewController {
var ids = [String]()
var titles = [String]()
override func viewDidLoad() {
super.viewDidLoad()
// Register cell classes
self.collectionView!.register(SupportersCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
getSupporters()
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return self.titles.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! SupportersCollectionViewCell
print("Hello collectionView")
let url_img = URL(string: self.titles[indexPath.row])!
print(url_img)
cell.img_logo.af_setImage(
withURL: url_img
)
return cell
}
func getSupporters() {
RestApiManager.sharedInstance.getSupporters { (json: JSON) in
if let results = json.array {
for entry in results {
// print(entry["title"])
//self.ids.append(entry["id"].string!)
self.titles.append(entry["title"].string!)
}
print(self.titles.count)
DispatchQueue.main.async{
self.collectionView!.reloadData()
}
}
}
}
}
First, you don't required this:
#IBOutlet var collection_supporters: UICollectionView!
Because your view controller is inherited from UICollectionViewController
Also I changed the register of the UICollectionViewCell class to this so it won't crash when you the cells are created:
self.collectionView!.register(SupportersCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
I have a reusable collection view cell. I am trying to set an image at didselect at index path but when i select a cell it return more than cell returning same image.
Here is the code.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return 1
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CollectionViewCell
cell.userImage.layer.cornerRadius = cell.userImage.frame.size.width/2
cell.userImage.clipsToBounds = true
return cell
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int{
return 10
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
let cell = collectionView.cellForItemAtIndexPath(indexPath) as! CollectionViewCell
cell.userStatusImage.image = UIImage(named: "selectedcontact.png")
collectionView.reloadData()
}
You need to keep track of the selected cells.
var selectedIndexes = [Int]()
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath)
if self.selectedIndexes.contains(indexPath.row){
cell.backgroundColor = UIColor.redColor()
}
else{
cell.backgroundColor = UIColor.whiteColor()
}
return cell
}
// MARK: UICollectionViewDelegate
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
if selectedIndexes.contains(indexPath.row){
selectedIndexes.removeObject(indexPath.row)
}
else{
selectedIndexes.append(indexPath.row)
}
collectionView.reloadData()
}
Swift array don't have method to removeObject. Here is an extension for that
extension Array {
mutating func removeObject<U: Equatable>(object: U) -> Bool {
for (idx, objectToCompare) in self.enumerate() {
if let to = objectToCompare as? U {
if object == to {
self.removeAtIndex(idx)
return true
}
}
}
return false
}
}
It seems the last line of code collectionView.reloadData() will reload all the cell to initial status since you don't have any place checking whether one specific cell is selected or not.