I have a UITableView with draggable rows and I can add/remove items. The datasource is a NSMutableArray.
Now, if I move the row with "Add new functionality" the app crashes because the dataSource is smaller since such row has not been added yet.
So I've modified this code:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row >= [dataList count]) return NO;
return YES;
}
And now I can't move it anymore. However I can still move the other rows after such row and consequently the code crashes.
How can I solve this ? Is there a way to disable the dragging "to" specific rows and not only from ?
thanks
This is exactly what the UITableViewDelegate method
-tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:
is for. Will it suit your purposes? Here's the documentation.
The previous answers and the documentation (see this and this, as mentioned in the other answers) are helpful but incomplete. I needed:
examples
to know what to do if the proposed move is not okay
Without further ado, here are some
Examples
Accept all moves
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
return proposedDestinationIndexPath;
}
Reject all moves, returning the row to its initial position
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
return sourceIndexPath;
}
Reject some moves, returning any rejected row to its initial position
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (... some condition ...) {
return sourceIndexPath;
}
return proposedDestinationIndexPath;
}
How to make last row fix:
- (NSIndexPath *)tableView:(UITableView *)tableView
targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
// get number of objects
NSUInteger numberOfObjects = [[[BNRItemStore sharedStore] allItems] count];
if ( (proposedDestinationIndexPath.row+1==numberOfObjects) || (sourceIndexPath.row+1==numberOfObjects) ) {
NSLog(#"HERE");
return sourceIndexPath;
}
else{
NSLog(#"count=%d %d", [[[BNRItemStore sharedStore] allItems] count], proposedDestinationIndexPath.row);
return proposedDestinationIndexPath;
}
}
Following is the example to restrict drag and drop to 0th index of 1st section UICollectionView:
func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
if session.localDragSession != nil {
// Restricts dropping to 0th index
if destinationIndexPath?.row == 0 {
return UICollectionViewDropProposal(operation: .forbidden)
}
if collectionView.hasActiveDrag {
return UICollectionViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
} else {
return UICollectionViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
}
}
}
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
// Prevents dragging item from 0th index
if indexPath.row == 0 {
return [UIDragItem]() // Prevents dragging item from 0th index
}
let item = self.yourArray[indexPath.row]
let itemProvider = NSItemProvider(object: item)
let dragItem = UIDragItem(itemProvider: itemProvider)
dragItem.localObject = item
return [dragItem]
}
Following is the working solution for disable row from moving.
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
if indexPath.row == 0{//specify your indexpath row here....
return false
}else{
return true
}
}
Related
I want to ask, how can I forward to edit mode, where I can to select multiple rows, as in Messages app, when you click on top right button "Select", when you can choose multiple messages by tapping on circles.
Like this:
I searched a lot, really, but couldn't find anything. Can anyone help me? Some advices
Set
tableView.allowsMultipleSelectionDuringEditing = true
Screenshot
Demo code
class TableviewController:UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
tableView.setEditing(true, animated: false)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
If you're using Storyboards do this:
NSMutableArray *selected;
decleare it in you viewcontroller.h file..
selected =[[NSMutableArray alloc]init];
for (int i=0; i<[YOUR_ARRAY count]; i++) // Number of Rows count
{
[selected addObject:#"NO"];
}
add same number of "NO" in selected array using above code for that you had to replace YOUR_ARRAY with your data array that you show in table.
if(![[selected objectAtIndex:indexPath.row] isEqualToString:#"NO"])
{
cell.accessoryType=UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType=UITableViewCellAccessoryNone;
}
put above code in your -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)path {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:path];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[selected replaceObjectAtIndex:path.row withObject:#"YES"];
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
[selected replaceObjectAtIndex:path.row withObject:#"NO"];
}
}
also put this to work correctly..
Is it possible to make editable the UITableView by adding the commitEditingStyle method only under certain circumstances ?
I have a controller.m/.h file that is doing stuff for 3 differents storyboards viewcontrollers. I want only 2 of the 3 to be able to commitEditingStyle. I can distinguish them using the self.restorationIdentifier.
you can check tableview tag..
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableview.tag==1 || tableview.tag==2)
return UITableViewCellEditingStyleDelete;
return UITableViewCellEditingStyleNone;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
//here your code
}
}
public override UITableViewCellEditingStyle EditingStyleForRow(UITableView tableView, NSIndexPath indexPath)
{
//here we show and hide the delete for particular row
if (indexPath.Row ==1)
{
return UITableViewCellEditingStyle.Delete;
}
else {
return UITableViewCellEditingStyle.None;
}
}
public override void CommitEditingStyle(UITableView tableView, UITableViewCellEditingStyle editingStyle, NSIndexPath indexPath)
{
if (editingStyle == UITableViewCellEditingStyle.Delete)
{
//here we handle delete button action of the tableview
}
}
Well, seems that I had simply to subclass and add the commitEditingStyle on the subclass. Then change the class in the storyboard to the subclass and that's all.
I am trying to control context of tableview with segmented control(which is above tableview). On change segment I want to reload data in my table tableView with different cells ( I have two types of cells and two data arrays for every type). How to clear and reload data with different cell ?
I'm just providing a few lines of code to what #jaydee3 has mentioned.
- (IBAction)segmentControlValueChanged:(UISegmentedControl *)sender
{
//Do something
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.segmentControl.selectedSegmentIndex?[self.dataArray2 count]:[self.dataArray1 count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.segmentControl.selectedSegmentIndex){
// Create Type cell for selected Index 1
}
else{
// Create Type cell for selected Index 0
}
}
In your tableView delegate, check what is selected in the segmented control and return the correct cells.
If the segmented control gets changed, call reloadData on the tableView.
1.Below is most important line if you want to change the data in 2 or more tableview.
#IBAction func segmentValueChanged(_ sender: UISegmentedControl)
{
self.<YourTableViewName>.reloadData()
}
2.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var index = <segmentObjectCreated>.selectedSegmentIndex
if index == 0 {
return <table1ArrayList>.count
}
else{
return <table12ArrayList>.count
}
}
3.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = <TableViewObj>.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var index = <segmentObjectCreated>.selectedSegmentIndex
if index == 0 {
cell.textLabel?.text = <table1ArrayList>[indexPath.row].productName
}
else {
cell.textLabel?.text = <table2ArrayList>[indexPath.row].newLaunch
}
return cell
}
That's it, Happy Coding!
I want to block a cell to be reordered.
For example:
I have a tableview with 4 rows but don't want that the first cell can be reordered.
Is that possible?
I've tried to use:
if(indexPath.row == 0)
{
[cell setEditing:NO animated:NO];
}
But doesn't work.
Thanks.
I found the answer!
You can use the method targetIndexPathForMoveFromRowAtIndexPath from UITableViewDelegate.
-(NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath{
if (proposedDestinationIndexPath.row == 0) {
return sourceIndexPath;
}
return proposedDestinationIndexPath;
}
Your UITableViewDataSource should implement -(BOOL)tableView:canMoveRowAtIndexPath: and return NO for indexPath.row == 0
- (BOOL)tableView:canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return (indexPath.row != 0);
}
UITableViewDataSource documentation
SWIFT 5 CODE:
func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if proposedDestinationIndexPath.row == 0 {
return sourceIndexPath
}
return proposedDestinationIndexPath
}
I was hitting my head over this one, and google was turning up nothing.
I eventually worked it out and thought I'd write it up here for the sake of the next person.
You have a UITableView with multiple sections. Each section is homogeneous, but the table overall is heterogeneous. So you might want to allow re-ordering of rows within a section, but not across sections. Maybe you only even want want one section to be reorderable at all (that was my case).
If you're looking, as I was, at the UITableViewDataSourceDelegate you won't find a notification for when it is about to let you move a row between sections. You get one when it starts moving a row (which is fine) and one when it's already moved it and you get a chance to sync with your internal stuff. Not helpful.
So how can you prevent re-orders between sections?
I'll post what I did as a separate answer, leaving it open for someone else to post an even better answer!
This implementation will prevent re-ordering outside of the original section like Phil's answer, but it will also snap the record to the first or last row of the section, depending on where the drag went, instead of where it started.
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (sourceIndexPath.section != proposedDestinationIndexPath.section) {
NSInteger row = 0;
if (sourceIndexPath.section < proposedDestinationIndexPath.section) {
row = [tableView numberOfRowsInSection:sourceIndexPath.section] - 1;
}
return [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section];
}
return proposedDestinationIndexPath;
}
Simple enough, really.
The UITableViewDelegate has the method:
tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:
This gets called while the user is hovering over a potential drop point.
You get a chance to say, "no! don't drop it there! Drop it over here instead". You can return a different index path to the proposed one.
All I did was check if the section indices match. If they do then great, return the proposed path. if not, return the source path.
This also nicely prevents the rows in other sections even moving out of the way as you drag - and the dragged row will snap back to it's original position of you try to move it to another section.
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if( sourceIndexPath.section != proposedDestinationIndexPath.section )
{
return sourceIndexPath;
}
else
{
return proposedDestinationIndexPath;
}
}
Swifty swift version of Jason's answer for you lazy people:
Swift 3, 4 & 5
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return IndexPath(row: row, section: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}
Swift 1 & 2
override func tableView(tableView: UITableView, targetIndexPathForMoveFromRowAtIndexPath sourceIndexPath: NSIndexPath, toProposedIndexPath proposedDestinationIndexPath: NSIndexPath) -> NSIndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return NSIndexPath(forRow: row, inSection: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}
You can prevent the movement of rows in between section using below method.
Just do not allow any movement in between section.
You can even control the movement of specific row within a section. e.g last row in a Section.
Here is the example:
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath {
// Do not allow any movement between section
if ( sourceIndexPath.section != proposedDestinationIndexPath.section) {
return sourceIndexPath;
}
// You can even control the movement of specific row within a section. e.g last row in a Section
// Check if we have selected the last row in section
if (sourceIndexPath.row < sourceIndexPath.length) {
return proposedDestinationIndexPath;
}
else {
return sourceIndexPath;
}
}
Swift 3:
override func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
if sourceIndexPath.section != proposedDestinationIndexPath.section {
var row = 0
if sourceIndexPath.section < proposedDestinationIndexPath.section {
row = self.tableView(tableView, numberOfRowsInSection: sourceIndexPath.section) - 1
}
return IndexPath(row: row, section: sourceIndexPath.section)
}
return proposedDestinationIndexPath
}
Than #Jason Harwig, the code below works correctly.
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if (sourceIndexPath.section != proposedDestinationIndexPath.section) {
NSInteger row = 0;
if (sourceIndexPath.section < proposedDestinationIndexPath.section) {
row = [tableView numberOfRowsInSection:sourceIndexPath.section] - 1;
}
return [NSIndexPath indexPathForRow:row inSection:sourceIndexPath.section];
}
return proposedDestinationIndexPath;
}
For not changing position between sections Swift3
override func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
if originalIndexPath.section != proposedIndexPath.section
{
return originalIndexPath
}
else
{
return proposedIndexPath
}
}