NSCollectionViewItems are not visible - swift

I am trying to show items in a subclass of NSCollectionViewItem in an NSCollectionView. I can see that the NSCollectionViewDataSource gives the right number of items and is called the right number of times
extension Document: NSCollectionViewDataSource {
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.attachedFiles?.count ?? 0
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let attachment = self.attachedFiles![indexPath.item]
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "AttachmentCell"), for: indexPath)
item.imageView?.image = attachment.thumbnailImage
item.textField?.stringValue = attachment.fileExtension ?? ""
return item
}
}
but the items are not shown in the GUI. I check that item.view.isHidden is false, but is there anything I need to do to make the items visible? Of course, there is also the possibility that they are never added to the collection even though the data source functions are called, but in that case my question would be how to fix that?

Related

How to programmatically set up a dynamic number of collection views in Swift

I have a list of many Decks, which is mainly has a name and a list of Cards. The below shows the class structure.
class Deck{
var id:String
var name:String
var cards:[Card]
init(id:String, name:String, cards:[Card]) {
self.id = id
self.name = name
self.cards = cards
}
}
class Card {
var id:String
var name:String
init(id:String, name:String){
self.id = id
self.name = name
}
}
I need to be able to create a single UICollectionView for each deck, where each cell should represent a button with the single card's name, and I need to be able to identify which cell's button is tapped. This means that I need to allow both dynamic number of UICollectionView(list of decks) and a dynamic number of UICollectionViewCell(list of cards). Please note that I already understand how to create the cells dynamically and identify which cell's buttons are tapped.
Overall, the main issue lies in the fact that I won't know how many decks and hence how many UICollectionViews I have to create. But, since the structure across all UICollectionViews are the same but populated with different values (i.e number of cells, cell's label title has different values), I am wondering what is the way to dynamically create UICollectionViews (NOT UICollectionViewCells) and populate them with decks from a list like decks:[Deck]
Thanks a bunch! The image at the bottom shows roughly what I would like to do.
It's still not clear if you really need a collection of UICollectionView objects or you could do with multiple sections in the same collection view. Anyhow, if you do like multiple collection views just make a collection or table view displaying decks list and put collection view for the deck in the cell.
class DeckCell: UICollectionViewCell { // or class DeckCell: UITableViewCell
var deck: Deck = .empty
#IBOutlet weak var collectionView: UICollectionView!
}
extension DeckCell: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.deck.cards.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath)
cell.card = self.deck.cards[indexPath.row]
return cell
}
}
Here is simple solution for you.
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//number of items in Decks Array will be its count
if collectionView == dockCollectionView {
return Decks.count //It will tell the collection view how much cell will be there
}
if collectionView == cardsCollectionView {
return Cards.count
}
}
//Setup your resuable cell here
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
//Setup for Dock CollectionView
if collectionView == dockCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Dock Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Decks[indexPath.row].name
return cell
}
//Setup for Cards CollectionView
if collectionView == cardsCollectionView {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cards Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = Cards[indexPath.row].name
return cell
}
}
//Now get to know which cell is tapped
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if collectionView == dockCollectionView {
print(Decks[indexPath.row].name)
}
if collectionView == cardsCollectionView {
print(Cards[indexPath.row].name)
}
// Here you write a code when user select a particular cell
}
You already have an solution you have a list in which you have a items so you have to implement dynamically so you just need to count a number of item's in a list.
So for do that you use items.count in to func:-
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cell Identifier Name", for: indexPath)
// This will return a each item name from the item list and display in to a cell
cell.textLabel.text = item[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Here you write a code when user select a particular cell
}

1 section and 2 cells but 4 indexPath.row

Swift 4.
I want to return two cells in one section, so in my class CollectionViewController I do :
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1 }
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 2 }
I see two cells but if I print indexPath.row in the code below (still in the same class), I see 0 1 0 1. Why is it not only one 0 1as I have only two cells in one section ?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! CollectionViewCell
print(indexPath.row)
return cell
}
cellForItemAt is getting called four times. Your numberOfItemsInSection is hard-coded for 2. When the view loads cellForItemAt is called twice (once for each cell). It is then getting called twice again when you call reloadData() from your DispatchQueue.main.async closure.
Updated - How to avoid the first call:
You need to store your cell data in an array, and only populate the array just before you call reloadData(). Thus, the array will be empty when the view is first loaded.
var yourArray = [YourObject]() //Empty Array
//..
DispatchQueue.main.async {
//append your two items to the yourArray
yourArray.append(/*cell1data*/)
yourArray.append(/*cell2data*/)
self.collectionView?.reloadData()
}
//..
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return yourArray.count
}

Swift - UICollectionView reloadItems takes too long

I want to reload only single cell after user select the contact (method didSelect contact). If the number of item in cell getting bigger, the process is getting slower. I also try to implement in DispatchQueue.main.async { code } and it still take some time to load.
cellForItemAt and noOfItemInSection (AddCell) - Total cell 4
var indexPaths = [IndexPath]()
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
indexPaths.append(indexPath)
if indexPath.item == 1 {
let addCell = collectionView.dequeueReusableCell(withReuseIdentifier: CellId, for: indexPath) as! AddCell
addCell.btnAdd.addTarget(self, action: #selector(handleOpenContact), for: .touchUpInside)
addCell.invitees = invitees
return addCell
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
cellForItemAt and noOfItemInSection (SubAddCell)
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: subAddId, for: indexPath) as! SubAddCell
cell.delegate = self
cell.invitee = invitees[indexPath.item]
return cell
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return invitees.count
}
didSelect (ContactPicker)
func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) {
let invitee = Invitee()
invitee.name = contact.givenName
if contact.isKeyAvailable(CNContactPhoneNumbersKey) {
for phoneNumber: CNLabeledValue in contact.phoneNumbers {
let a = phoneNumber.value
print("\(a.stringValue)")
invitee.phoneNo = a.stringValue
}
}
self.invitees.append(invitee)
for index in self.indexPaths {
if index == [0, 0] {
print(index)
self.collectionView?.reloadItems(at: [index])
}
}
}
I have obtain the value of indexPaths at cellForItemAt method. If the invitee contains 5 items, the print(index) will print 19 values. I dont know why. Below is the image.
Design of the UI
How to optimize the reloadItems?
Any help is really appreciated. Many thanks.
I think you have this indexPaths.append(indexPath) in cellForItemAt so that why the indexPaths can increase after reload data. You might use NSSet instead of NSArray to make sure your objects are unique.
And with this: cell.invitee = invitees[indexPath.item], did you implement didSet for invitee?
If you did, I think need to prepare data in background queue first. E.g.
DispatchQueue(label: "queue").async {
let image = <# process image here #>
let name = <# process name here #>
DispatchQueue.main.async {
self.imageView.image = image
self.label.text = name
}
}
Hope my tips could help you!
Collectionviews are the most powerful UI tool of iOS, but if done wrong, or if misused (which happens, don't worry), they can be worse for your UX. . To view how this stuff is affected read up on XCode Tools and how to use them to debug your UI performance.
Important note to consider:
frame-by-frame operations (ie, loading your collectionviewcell, expanded, or unexpanded) should take no more than 17 ms to execute for smooth scrolling.
UICollectionView performance - _updateVisibleCellsNow
So you need to make your cells as simple and efficient as possible. That's the best place to start, especially if your expandable cells themselves contain complex subviews like collectionviews.

Why does my UICollectionViewController not show anything?

I'm trying to make a UICollectionViewController show a grid of cells (in which I'll later add text, likely as textViews if possible). I have:
class GridViewController : UICollectionViewController{
//(NOW GONE) #IBOutlet weak var gridViewTable = UICollectionViewController()
let arr1 : [String] = []
let arr2 : [String] = []
override func viewDidLoad(){
super.viewDidLoad()
self.collectionView!.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "gridCell")
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 4
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.red
return cell
}
}
I used the interface builder to connect white where the cells will go to the gridViewTable. Nothing shows up at all, even tho it runs and compiles. How can I get the cells to show?
please use these code:
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 4
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath as IndexPath)
cell.backgroundColor = UIColor.red
return cell
}
You don't need the outlet for a UICollectionViewController, the class itself takes care of that.
Also, make sure that the view controller's class in Interface Builder is set to GridViewController in the Identity Inspector (3rd tab from the left in the right-most pane).

Passing Data to Custom CollectionView Cell (Static Data for all cells) SWIFT 2

So I have a collectionView in a viewcontroller. I have a variable var user = User! which stores the current selected user (FYI I'm using Parse). I want to pass variable to the custom cell.
extension SentViewController : UICollectionViewDataSource
{
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int
{
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
{
return cards.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Storyboard.CellIdentifier, forIndexPath: indexPath) as! SentCardCollectionViewCell
cell.card = self.cards[indexPath.item]
cell.delegate = self
cell.user = user
return cell
}
}
Now, my problem is that when I pass the variable user to the variable user in custom collection view cell, I get an object that repeats 3 times. i.e.: the same variable gets passed over, but it is multiplied 3 times.
Any idea how to solve this? I have a feeling that it has something to do with the indexPath. Anyone have solutions?