Hide view in specific CollectionViewCell - ios

I have question how to implement this stuff in right way.
So far I done
if indexPath.row == 1 {
let indexPatha = NSIndexPath(forRow: 0, inSection: 0)
let changeCell = collectionView .cellForItemAtIndexPath(indexPatha) as! BarCollectionViewCell
changeCell.addNewBottleSecondButton.alpha = 0
}
But when I swipe until cell is hidden, I am getting error, unexpectedly found nil while unwrapping an Optional value, and still this doesn't looks like how I want to make it.
I want to achieve that when I have more then one cell, I want to hide one specific view.

Would it work in your flow to handle this in cellForRowAtIndexPath instead?
After your initialize your cell:
cell.addNewBottleSecondButton.hidden = (indexPath.row == 0) && (dataItems.count > 1)

If the cell has been scrolled off the screen it may not exist anymore due to Apple's dequeue/re-use optimizations. Previously when confronted with this problem, I've had to set a state variable and handle the UI change in cellForRow if the cell didn't exist when trying to change the cell's UI.

Related

Why does referencing an item that exists in a collection view return a nil value?

let x = X(name: "x")
blocks.append(newBlock)
let indexPath = IndexPath(row: blocks.count - 1, section: 0)
collectionView.insertItems(at: [indexPath])
let aCell = collectionView.cellForItem(at: indexPath) as! CollectionViewCell
The above code is in a function that runs when a button is pressed in the view controller to present a new item in the collection view. As collectionView.insertItems(at: [indexPath])adds a new item at the specific index, I don't understand why let aCell = collectionView.cellForItem(at: indexPath) as! CollectionViewCellwould return a nil value. The specific error is "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value". I only get this error when the cell is outside of the screen. Meaning that if I add an item when there are few cells on the screen or if I scroll to the bottom if there are many cells and add a new item, it'll work. However, If there are many items and I do not scroll down, it'll have that error
The documentation for cellForItem(at:) says this:
this method returns nil if the cell isn't visible or if indexPath is out of range
You're getting a nil value because your cell is outside of the screen, and the force-cast is causing a crash. You should avoid force-casting in general.
Update
I think you might be unfamiliar with the way tables and collection views work. They don't store a cell for every row/item that you have, instead they create a pool of cells and reuse them.
When the cell goes out of bounds, the table/collection view stops associating it with the IndexPath and, as the user scrolls, this old cell is used again for the new IndexPath that needs to be displayed.
In general, you should configure your cell completely in collectionView(_:cellForItemAt:), and only use cellForItem(at:) to update a visible cell when the content that should be displayed on it changes.
There are 2 similar methods. UICollectionView implements the method cellForItem(at:) and the UICollectionViewDataSource protocol defines the method collectionView(_:cellForItemAt:) They do different things.
The UICollectionView method cellForItem(at:) will return a cell at the specified IndexPath if it is on screen and not out of range. It's meant for fetching cells that are currently visible.
If you call that method when the first 5 cells of your single-section collection view are visible, and ask for the indexPath of the 6th cell, you will get a nil back. That is what's happening in your code. You're trying for force cast nil to CollectionViewCell, so your code crashes. Don't do that. Check to see if you get back a nil, and handle nil gracefully.
If you scroll down so that cell 6 is visible and then ask for cell 6, it will be returned. If it's currently off-screen, it will return nil. As #EmilioPelaez says in his answer (voted), that's how the function works.
The other method collectionView(_:cellForItemAt:) is the method the collection view calls to ask its data source to create and configure cells. That method must always return a cell for any valid IndexPath in your model. You should't call that method directly though.

Removing Button From Section 0 in Tableview

I have a tableview that contains 4 sections. In sections 2,3,and 4 I want to have a + button to add information to a "Saved" array. I have the logic setup for adding information, but I'm having issues with the tableview cells.
I don't want the + button to appear in section 0, since that's where we're adding the data. Here's my cellForRowAt method...
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) as! SchoolTableViewCell
// Configure the cell...
if indexPath.section == 0 {
cell.textLabel?.text = "Test"
cell.addFavoritesButton.removeFromSuperview()
} else if indexPath.section == 1 {
cell.textLabel?.text = Items.sharedInstance.elementaryList[indexPath.row]
} else if indexPath.section == 2 {
cell.textLabel?.text = Items.sharedInstance.intermediateList[indexPath.row]
} else if indexPath.section == 3 {
cell.textLabel?.text = Items.sharedInstance.highschoolList[indexPath.row]
}
return cell
This works great at first! But if I scroll down, more and more cells will remove the button. It's not limiting it to section 0 because of reusable cells.
Can anyone think of a better way to remove this button for the first section only?
Screenshot of section 0
Screenshot of section 1
First run show the cells correctly because of all cells are new instances of the cell class (without reusing) , but after scroll shown cells may be reused with a possibility that this reused cell be the one in section zero which you removed the button from it , You can try to show/hide it
if indexPath.section == 0 {
cell.textLabel?.text = "Test"
cell.addFavoritesButton.isHidden = true
}
else
{
cell.addFavoritesButton.isHidden = false
}
You are forgetting that cells are reused. You need to deal, every time thru cellForRowAt, with the possibility that this cell already has the button from a previous use and should not have it in this use, or with the possibility that it lacks the button and needs it in this use.
For example, you cannot assume that just because the section is 1, the cell has the button, because it might have been used in section 0 earlier and lacks the button now. You need, in that case, to add it. But you are not doing that.
Thus, for every branch of your logic, you must be explicit about whether to add or remove the button. If you are really going to add and remove it, that can get complicated. You would need to keep a copy of the button somewhere, so you can add it. You'd make sure you don't add it twice to the same cell. You'd make sure you don't try to remove it if it is already removed.
As has been suggested in another answer, the simpler way to deal with this is not to add and remove at all, but to make visibility of the button dependent on whether this section is 0:
// do this in _every_ case
cell.addFavoritesButton.isHidden = (indexPath.section == 0)
That's a single line of code that does, much better, the thing you are trying to do.
Once you remove the button from the cell by calling cell.addFavoritesButton.removeFromSuperview(), it would not be added back again for you when the cell is reused. You should keep the button on the cell, but hide it with
cell.addFavoritesButton.isHidden = indexPath.section == 0
or add a new feature that lets end-users remove items from section zero, and change the picture on the button from + to -:

fatal error: unexpectedly found nil while unwrapping an Optional value in Tableview

I had implemented UITableView with custom cell.It works fine.
Tableviewcell have 3 textfields in every cell.
I try to access all cell on submit button.
But app is crash due to access non-visible cell.
How can I got all cell's all 3 textfield values even they are not visible.
Thank you,
UITableView's cellForRowAtIndexPath method will return nil if cell not visible. You should check for this, and perform only data source changes if so:
if let cell_note = self.tbl.cellForRowAtIndexPath(NSIndexPath(forRow: 0, inSection: 4)) as? NotesCell
{
// do something with your data source
// do something with cell_note that already loaded on screen
}
else
{
// do something with your data source
// your cell didn't loaded yet
// you should only prepare fresh information in your data source for delegate method tableView(_:cellForRowAtIndexPath:)
}
Note that if you have same data source changes for both cases (as usual in fact), you need move them out of this check

Not getting all UITableViewCell from UITableView

I have a custom cell within my UITableView with a textField in it. I'm trying to read the textFields' values from table view.
To get the cells and values from textFields, I'm using the below code:
for var i = 0; i < myTableView.numberOfRowsInSection(0); i++ { //5 records
let index: NSIndexPath = NSIndexPath(forRow: i, inSection: 0)
//getting the cell at position i
let cell = self.myTableView.cellForRowAtIndexPath(index) as! myCustomCell
//Read data
let newValue = cell.myTextField.text
}
I'm not getting any compiler errors, but when I run the application, it sometimes fails at cell index 0, sometimes at cell index 3, it is at a random position. The error I'm getting is:
fatal error: unexpectedly found nil while unwrapping an Optional value
Which I understand because the cell is not present within myTableView.
Can anyone point me in the right direction to be able to read all the custom cells from my table view?
UITableViewCells are getting recycled. That's why its not safe to do it your way. You need to use a Data Model as others already pointed out.

Why is my custom collection view cell unexpectedly nil after selecting cell in code?

So I have a colectionView of images, and when something happens in the background, I might try to select a specific custom collectionViewCell using the method:
self.collectionView.selectItemAtIndexPath(indexPathToReload, animated: true, scrollPosition: UICollectionViewScrollPosition.CenteredVertically), which works fine, the collectionView scrolls to the desired location.
However, if I then try to actually update the appearance of the cell as it's been updated by calling self.collectionView(self.collectionView, didSelectItemAtIndexPath: indexPathToReload) I get an unexpectedly nil cell when I then try to create the cell in didSelectItemAtIndexPath.
I partially understand why this method of updating cells is unsafe (as I've read elsewhere in researching this question like here in one of the answers.)
Thus, the crash makes me assume that cells are not part of the visible cells on the screen, which is why the cell is nil when I try to create it. But this doesn't make sense as I also assume that the cells have to be created in order to be scrolled to, which as I said works fines because they are created as expected and can be interacted with without issue.
So why is my cell nil? Or why is my collection view not thinking that the cell that was scrolled to not part of the visible cells? And if the reason is obvious, then how can I make a cell update it's appearance when I select it in code?
EDIT: Code in context
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.selectItemAtIndexPath(indexPathToReload, animated: true, scrollPosition: UICollectionViewScrollPosition.CenteredVertically)
self.collectionView(self.collectionView, didSelectItemAtIndexPath: indexPathToReload)
return
}
As I've said, this pretty much is the context. In the first line I may scroll to an index that is not visible on the screen. If I do this, and then the second line of code executes, the cell that is created in the delegate method that is called is unexpectedly nil.
In order to fix this issue, I had to use kind of a hacky workaround, which although works seems almost too dirty, and I don't know why this issue hasn't been addressed by Apple (i.e why selectItemAtIndexPath doesn't call the delegate method didSelectItemAtIndexPath). Anyways, what I ended up doing was when I needed to update my selected cell in background, I first got the index and set a bool to show a cell was selected in code:
dispatch_async(dispatch_get_main_queue()) {
self.collectionView.selectItemAtIndexPath(indexPathToReload, animated: true, scrollPosition: UICollectionViewScrollPosition.CenteredVertically)
let cell = self.collectionView.cellForItemAtIndexPath(indexPathToReload) as? ListingCollectionViewCell
if cell != nil {
self.collectionView(self.collectionView, didSelectItemAtIndexPath: indexPathToReload)
return
} else {
self.buttonSelectedInCode = true
self.indexPathSelectedInCode = indexPathToReload
return
}
}
Above, I had to try to create the cell for the specified index path. If the cell isn't nil then I know that the cell is visible, and it's safe to call on the delegate didSelectItemAtIndexPath. However, if the cell is nil, then I have to set up my bool and index, and wait for the scroll view to call the delegate method, as shown below.
Then, I further implemented scrollViewDidEndScrollAnimation and used the call of this delegate method to then select my cell in code, as follows:
func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) {
if buttonSelectedInCode {
self.collectionView(self.collectionView, didSelectItemAtIndexPath: self.indexPathSelectedInCode)
}
}

Resources