UITableView insertion control should not be reorderable - ios

I have a UITableView where I want to be able to insert rows via the Insertion Control (green plus icon). (I originally had this working with the Add control on the navigation bar, but with 'back', 'add', and 'edit' controls I felt the navigation bar was a bit cluttered).
I got this working, but I don't like the way this interacts with the reorder control. My table only has one section; the insertion row will always be shown as the last row in the table, and it should not be reorderable.
I implemented canMoveRowAtIndexPath: to return false for the insertion control row, but this is only a partial solution. That removed the ability to drag the insertion row. However, nothing prevents a user from selecting a movable row and dragging it beneath the insertion controller row. (And if allowed, this crashes the app).
The workaround I've implemented is to check the to location in moveRowAtIndexPath:toIndexPath. If the destination location is below the insertion row, I do not perform the move, and I call [tableView reloadData] to redraw the previous table state. This works, but the appearance is clunky -- the row just snaps back to its previous position with no indication why.
Is this the correct behavior here -- or is there a more elegant solution? Ideally, I'd like the user to not be able to drag the row to an illegal location in the first place, but I'm not sure there is any mechanism to prevent it.

You need to implement the tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath: delegate method. The implementation should return an appropriate index path based on whether the row can be targeted to the desired row or not.

Here's the implementation:
// Don't allow dragging any moveable row below the insertion control
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath: (NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
int proposedRow = proposedDestinationIndexPath.row;
int maxRow = [[[SADeckStore sharedStore] allDecks] count] - 1;
if (proposedRow < maxRow)
return proposedDestinationIndexPath;
else
return [NSIndexPath indexPathForRow:maxRow inSection:[proposedDestinationIndexPath section]];
}

Related

Disable prepareForReuse for selected cell

I have a custom UITableViewCell. When a cell gets selected, a UILabel gets added to it. I had to use prepareForReuse for it not to get messy, like so:
- (void)prepareForReuse {
NSArray *viewsToRemove = [self.view subviews];
for (UILablel *label in viewsToRemove) {
[label removeFromSuperview];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CategorieCell *customCell = [tableView dequeueReusableCellWithIdentifier:#"cellID" forIndexPath:indexPath];
return customCell;
}
The problem is when I scroll down enough that the label is out of view, and then I scroll back up, the label isn't there anymore. The reason is obviously because when the cells get reused, I removed all the labels.
So is there a way to disable prepareForReuse (or just the code in the method) for the selected row, and how?
Cells that are scrolled away will be reused, and there's no way around it. Even if you avoid the removeFromSuperview logic, that cell will reappear at a different index path, probably not where you want it.
The way to conditionally configure cells is in cellForRowAtIndexPath. There, you can ask if the indexPath is among the table view's indexPathsOfSelectedCells. If it is, then configure it with the extra labels, or not, if not.
One way to reduce the messiness is to have those labels remain in the cell unconditionally, just setting their alphas to 0 or 1, depending on the selection state.
For example, in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
// if you know the table has singular selection
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
BOOL rowIsSelected = [indexPath isEqual:selectedIndexPath];
// OR, for multiple select...
NSArray *selection = [tableView indexPathsForSelectedRows];
BOOL rowIsSelected = [selection containsObject:indexPath];
// now either conditionally create/destroy or show/hide the subviews
// that appear on selection (I prefer show/hide for simpler cells)...
[cell configAsSelected:rowIsSelected]; // have the custom cell do it
// in that method, or here, if you're less OO-inclined...
cell.subviewThatAppearsOnSelected.alpha = (rowIsSelected)? 1.0 : 0.0;
The larger point is, this is the suggested place to reliably configure a cell based on the model and its current position in the table
Think of table cells as dumb containers that get reused to hold different things (labels, images, buttons, etc.).
You fill the cells in cellForRowAtIndexPath.
You empty them in prepareForReuse so they can be filled again and reused.
Don't confuse these two actions.
When you fill the cell, you should be filling it from data that you have stored somewhere else - i.e. not from other cells. If you are relying on indexPathsOfSelectedCells to help you when filling your cell, you are going to have problems. Don't do this.
Typically you would have an array of objects, where each object corresponds to a cell. You have as many cells in your table as objects in the array. You might initialize the objects in your array in viewDidLoad, or pass them in from a previous view controller.
This process doesn't have to be complicated. Most cells display only a few bits of data, so your object (often called a model) doesn't have to have many properties to hold this data.
When the user selects a cell, set a "selected" property in its corresponding object to indicate this. This value stays around in the object even when the cell is scrolled off the screen and reused. That's good.
Now when the user scrolls back to the cell, you fill the cell with data from the corresponding object. Since that object has its "selected" property set, you "fill" the cell by adding the label that you want there in this case. Or if it isn't set, you don't add the label.
In prepareForReuse, always remove the label to put the cell in its empty state, ready to be refilled.

How can I get index path of cell on switch change event in section based table view

I have custom cell having switch in few cells at right side. what I want is to store value of specific cell on switch change event. Table view has number sections so I can't set tag for switch because I need section as well as row to obtain index path.
Any suggestion any alternative but I have to use UISwitch in section based table view.
Thanks
In your custom cell add properties which help you identify the information the cell represents. Index path, indexes for your data model etc...
Then add a block property to the cell which you can call to tell a UITableView or any other piece of code when a cell switch changes. e.g.
#property (nonatomic,copy) void (^onSwitchChange)(UITableViewCell *cell);
Inside your custom cell code, add an action handler for the UISwitch. When it fires, call self.onSwitchChange(self) which will notify the code which registered an onSwitchChange block that a switch has changed and on which cell.
In your table view when you create the cell, set the onSwitchChange block as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath (NSIndexPath *)indexPath
{
<snip>
YourUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:yourCellIdentifier forIndexPath:indexPath];
cell.onSwitchChange=^(UITableViewCell *cellAffected){
// Add code to deal with the swicth switch using properties of cellAffected
... Your handler code here ...
}];
<snip>
}
This lets you handle all the changes in the table view controller. Hope this helps.
The answer from #Jageen works. I had to find out which superview the cell is, mine was one more level higher.
UITableViewCell *cell = (UITableViewCell*)[sender superview].superview.superview;
NSIndexPath *indexPath = [self.myTableView indexPathForCell:cell];
NSLog(#"Section %ld Row : %ld",(long)indexPath.section,(long)indexPath.row); // print row section and index
You can still create tags even if you have sections if you have some idea of about max rows in a largest section. For example if you think there will be 1000 rows in a section then you can create tag using following formula.
tag = section * 1000 + row;
later in your IBAction of switch you can find out the indexpath (section and row) using following:
section = tag/1000;
row = tag%1000;
If you have no idea of how many rows your section will have you can find out the cell using sender.superview.superview (be careful if have added any other views in hierarchy).
Rory McKinnel's answer is still the cleanest solution for your problem.

Scroll to selected cell in UITableView after selecting cell in UISearchDisplayController?

I want to set up a UISearchDisplayController such that after a cell is selected from the search results, the search display controller is dismissed and the table view scrolls to the selected cell. Here's my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == self.searchDisplayController.searchResultsTableView)
{
[self.searchDisplayController setActive:NO];
// scroll to selected cell in table view here
}
}
I know the method I need to use is:
scrollToRowAtIndexPath:atScrollPosition:animated:
However, this method needs an index path from the main table view, not the search results table view. How can I get this index path?
What does your datasource look like? Perhaps you can query the datasource based on what was selected in the search results to get the cell's index.
Sounds like a strange user interaction not knowing what you're doing though. Normally searching and then selecting will take you to that record's details or select it as an option or something.

How to Implement Tab View in Apple Maps

I want to implement the Tab View like Apple has in their Apple Maps when you select a location and tap on the more details to reviews additional information about that location.
What would be the best way to implement this or how do you think Apple has implemented it. I know each tab view has a Table View inside each. Also for the Photos tab how do you think they implemented the content inside that tab. Was it using UICollectionView or a table view with a custom cell that has 4 photos in each cell row?
It looks like they're using one UITableView and on UISegmentedControl. With this combination every time the user selects and index on the segmented control, it changes a condition in the table's datasource and reloads the data. Here's an example of what something like that could look like to conditionally change the number of rows in the table
- (IBAction)segmentValueDidChange:(UISegmentedControl *)sender
{
NSLog(#"%d",sender.selectedSegmentIndex);
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.segment.selectedSegmentIndex == 0) {
return 5;
}
return 10;
}

Manage Swapping between two cells rather than changing the NSIndexpath of other UITableViewcells

I know it's the default property of rearrangement of tableviewcells when UITableView is in editing mode & user just reorder the Rows which is handled by UITableViewController.
As per Apple Doc : Reordering of Rows
Now what I want to do is, when I drag the cell over other cells, the backend cells should not get rearranged automatically, just the cell on which I drop the dragged cell should gets rearranged, a kind of Swapping between two cells.
For better understanding, please refer the below image. I should be able to Shift Jones to Room1 & the tableview controller should able to automatically be shifting Smith2 to Room3.
Below are delegates methods which I am using :
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath {
NSString *temp = [roomstoringArray objectAtIndex:sourceIndexPath.row];
[roomstoringArray removeObjectAtIndex:sourceIndexPath.row];
[roomstoringArray insertObject:temp atIndex:destinationIndexPath.row];
// [temp release];
}
Problem is :
How can I stop other cells to get rearranged automatically while I
am dragging the cells over other.
There none of the delegate method which gets called when one used to
Drag the cell over others, only method called is
moveRowAtIndexPath when one drop the cell on it. So, even can't check for some condition & didn't find any scope of this enhancement
within existing TableView.
Is there any property with the TableView where I can stop such kind of behavior to perform the swapping between two cells only where rest of rows remain at the same indexPath.Alos, can someone please help me out with some-other way,if any.
For Problem you mention, Please check UITableView Delegates from here.
Below methods are helpful to you.
targetIndexPathForMoveFromRowAtIndexPath
canMoveRowAtIndexPath
For Swap b/w two rows you can get idea by this answer.

Resources