How to prevent moving of a UITableViewCell to a specific index - ios

In my application, the user has the ability to move UITableViewCell rows around using the edit button and drag and drop.
I do not want the user to be able to move a cell to row 0. I already have this working for my NSMutableArray where if the row is 0 then don't rearrange the objects in collection. But even with that in place, the visible table still shows the cell at row 0.
How can I prevent this graphically?
I've tried the following:
-(NSIndexPath*)tableView:(UITableView*)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath*)sourceIndexPath toProposedIndexPath:(NSIndexPath*)proposedDestinationIndexPath
{
if(proposedDestinationIndexPath.row == 0)
{
return 1;
}
return proposedDestinationIndexPath;
}
But it crashes with a EXC_BAD_ACCESS error when I try to move cell at row 1 to row 0.

Your approach is correct. The problem is that tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath method should return NSIndexPath*, but you return an integer. The fix is simple:
if(proposedDestinationIndexPath.row == 0)
{
return [NSIndexPath indexPathForRow:1 inSection: proposedDestinationIndexPath.section];
}

Swift Version
if(proposedDestinationIndexPath.row == 0){
return IndexPath.init(row: 1, section: proposedDestinationIndexPath.section)}

Related

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 -:

How to get a smooth select/deselect animation in ios UITableView

I'm working on an iOS app using Xamarin and MVVM Cross.
I have a table view from which one row can be selected at a time, which is indicated using the Checkmark accessory on the UITableViewCell. I'm essentially following the answer from this: ✔ Checkmark selected row in UITableViewCell
The problem is that Reloading the table data (or even reloading just the two rows that changed) seems to interrupt the animation of De-selecting the row. I would like it to fade out nicely. For example, on an iPad, the behavior in Settings -> Notes -> Sort Notes By is exactly what I want. Touching a row highlights it, letting go moves the checkmark, and then the highlight fades out.
After messing with this for several hours, the only solution I found was to Reload the rows to update the checkmark, then manually Re-select and De-select the row. This works fine, but feels like a big hack. Is there a cleaner way to do this?
Here's the relevant snippets:
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
SetCellCheckmark(tableView, indexPath);
base.RowSelected(tableView, indexPath); //This handles de-select via the MvxTableViewSource DeselectAutomatically property
}
private void SetCellCheckmark(UITableView tableView, NSIndexPath newSelection)
{
if (newSelection == null)
return;
var rowsToUpdate = new NSIndexPath[_checkedRow == null ? 1 : 2];
rowsToUpdate[0] = newSelection;
if (_checkedRow != null)
rowsToUpdate[1] = _checkedRow;
_checkedRow = newSelection;
tableView.ReloadRows(rowsToUpdate, UITableViewRowAnimation.None);
tableView.SelectRow(newSelection, false, UITableViewScrollPosition.None); //This feels like a hack
}
private UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item)
{
var cell = TableView.DequeueReusableCell(MyCellType, indexPath);
if (cell == null)
return null;
if (indexPath == _checkedRow)
{
cell.Accessory = UITableViewCellAccessory.Checkmark;
}
else
{
cell.Accessory = UITableViewCellAccessory.None;
}
return cell;
}
If you have an answer in Objective-C, I could probably translate it into Xamarinland.

Hide view in specific CollectionViewCell

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.

Iterate over all the UITableCells given a section id

Using Swift, how can I iterate over all the UITableCells given a section id (eg: all cells in section 2)?
I only see this method: tableView.cellForRowAtIndexPath, which returns 1 cell given the absolute index, so it doesn't help.
Is there an elegant and easy way?
Note: I want to set the AccesoryType to None for all of the cells in a section, programatically, say: after a button is clicked, or after something happends (what happends is not relevant for the question)
I have the reference for the UITableView and the index of the section.
You misunderstand how table views work. When you want to change the configuration of cells, you do not modify the cells directly. Instead, you change the data (model) for those cells, and then tell your table view to reload the changed cells.
This is fundamental, and if you are trying to do it another way, it won't work correctly.
You said "I need the array of cells before modifying them…" Same thing applies. You should not store state data in cells. As soon as a user makes a change to a cell you should collect the changes and save it to the model. Cells can scroll off-screen and their settings can be discarded at any time.
#LordZsolt was asking you to show your code because from the questions you're asking it's pretty clear you are going about things the wrong way.
EDIT:
If you are convinced that you need to iterate through the cells in a section then you can ask the table view for the number of rows in the target section, then you can loop from 0 to rows-1, asking the table view for each cell in turn using the UITableView cellForRowAtIndexPath method (which is different than the similarly-named data source method.) That method will give you cells that are currently visible on the screen. You can then make changes to those cells.
Note that this will only give you the cells that are currently on-screen. If there are other cells in your target section that are currently not visible those cells don't currently exist, and if the user scrolls, some of those cells might be created. For this reason you will need to save some sort of state information to your model so that when you set up cells from the target section in your datasource tableView:cellForRowAtIndexPath: method you can set them up correctly.
For Swift 4 I have been using something along the lines of the following and it seems to work pretty well.
for section in 0...self.tableView.numberOfSections - 1 {
for row in 0...self.tableView.numberOfRows(inSection: section) - 1 {
let cell = self.tableView.cellForRow(at: NSIndexPath(row: row, section: section) as IndexPath)
print("Section: \(section) Row: \(row)")
}
}
Im using same way of iterating all table view cells , but this code worked for only visible cells , so I'v just add one line allows iterating all table view cells wether visible they are or not
//get section of interest i.e: first section (0)
for (var row = 0; row < tableView.numberOfRowsInSection(0); row++)
{
var indexPath = NSIndexPath(forRow: row, inSection: 0)
println("row")
println(row)
//following line of code is for invisible cells
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: false)
//get cell for current row as my custom cell i.e :roomCell
var cell :roomCell = tableView.cellForRowAtIndexPath(indexPath) as roomCell
}
* the idea is to scroll tableview to every row I'm receiving in the loop so, in every turn my current row is visible ->all table view rows are now visible :D
To answer my own question: "how can I iterate over all the UITableCells given a section id?":
To iterate over all the UITableCells of a section section one must use two methods:
tableView.numberOfRowsInSection(section)
tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section))
So the iteration goes like this:
// Iterate over all the rows of a section
for (var row = 0; row < tableView.numberOfRowsInSection(section); row++) {
var cell:Cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section))?
// do something with the cell here.
}
At the end of my question, I also wrote a note: "Note: I want to set the AccesoryType to None for all of the cells in a section, programatically". Notice that this is a note, not the question.
I ended up doing that like this:
// Uncheck everything in section 'section'
for (var row = 0; row < tableView.numberOfRowsInSection(section); row++) {
tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section))?.accessoryType = UITableViewCellAccessoryType.None
}
If there is a more elegant solution, go ahead and post it.
Note: My table uses static data.
Swift 4
More "swifty", than previous answers. I'm sure this can be done strictly with functional programming. If i had 5 more minutes id do it with .reduce instead. ✌️
func cells(tableView:UITableView) -> [UITableViewCell]{
var cells:[UITableViewCell] = []
(0..<tableView.numberOfSections).indices.forEach { sectionIndex in
(0..<tableView.numberOfRows(inSection: sectionIndex)).indices.forEach { rowIndex in
if let cell:UITableViewCell = tableView.cellForRow(at: IndexPath(row: rowIndex, section: sectionIndex)) {
cells.append(cell)
}
}
}
return cells
}
Im using this way of iterating all table view cells , but this code worked for only visible cells , so I'v just add one line allows iterating all table view cells wether visible they are or not
//get section of interest i.e: first section (0)
for (var row = 0; row < tableView.numberOfRowsInSection(0); row++)
{
var indexPath = NSIndexPath(forRow: row, inSection: 0)
println("row")
println(row)
//following line of code is for invisible cells
tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: false)
//get cell for current row as my custom cell i.e :roomCell
var cell :roomCell = tableView.cellForRowAtIndexPath(indexPath) as roomCell
}
* the idea is to scroll tableview to every row I'm receiving in the loop so, in every turn my current row is visible ->all table view rows are now visible
You can use
reloadSections(_:withRowAnimation:) method of UITableView.
This will reload all the cells in the specified sections by calling cellForRowAtIndexPath(_:). Inside that method, you can do whatever you want to those cells.
In your case, you can apply your logic for setting the appropriate accessory type:
if (self.shouldHideAccessoryViewForCellInSection(indexPath.section)) {
cell.accessoryType = UITableViewCellAccessoryTypeNone
}
I've wrote a simple extension based on Steve's answer. Returns the first cell of given type (if any) in a specified section.
extension UITableView {
func getFirstCell<T: UITableViewCell>(ofType type: T.Type, inSection section: Int = 0) -> T? {
for row in 0 ..< numberOfRows(inSection: section) {
if let cell = cellForRow(at: IndexPath(row: row, section: section)) as? T {
return cell
}
}
return nil
}
}

How to delete the only cell from UICollectionView with animation (deleteItemsAtIndexPaths)?

I'm getting an assertion while deleting a single UICollecitonViewCell from UICollectionView.
Precondition: I have a single cell (when I have two or more cells, the deletion works good).
Here is the code:
NSIndexPath *ip = [_photosCollectionView indexPathForCell:cell];
[_datasource removeItemAtIndex:ip.item];
[_photosCollectionView deleteItemsAtIndexPaths:#[ip]]; // the assertion is raised
Here is the assertion text:
NSInternalInconsistencyException: attempt to delete item 0 from section 0 which only contains 0 items before the update
Quite strange issue, because it works for 2, 3 or more cells, but when I delete a single cell, it fails.
Any ideas what's wrong, how to work-around this issue?
Thanks to similar questions and answers on SO, found a solution to use performBatchUpdates:
[_photosCollectionView performBatchUpdates:^ {
[_datasource removeItemAtIndex:ip.item];
[_photosCollectionView deleteItemsAtIndexPaths:#[ip]]; // no assertion now
} completion:nil];
Same solution with Swift 4.1
let indexPath = IndexPath(item: 0, section: 0)
self.collectionView.performBatchUpdates({
self.collectionView.deleteItems(at:[indexPath])
}, completion:nil)

Resources