Saving a placeholder in a UITableView

I have a UITableView that I never want to go below 1 cell: this is a directory reading, and if there are no files in the directory, it has a separate cell that says โ€œNo filesโ€. (In editing mode, there is a bonus cell for creating a file, so the editing mode never falls below two cells.)

Probably just a lack of sleep does not allow me to think about my way out of a paper bag right now, but I continue to get frustrated by such errors:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).' 

This happens because I pre-add the No Files placeholder before deleting the last file. There seems to be a crash if I delete the last cell of the file before adding the placeholder. In any case, the number of cells does not synchronize with the return from numberOfRowsInSection, and this causes a failure.

Of course, there is a design template for this situation. Connect me?

+4
source share
3 answers

Do something along the lines shown in the code snippet below:

  • first delete the data for the string from the array
  • If the elements of the array have not dropped to zero, then delete the row from the table
  • if the elements of the array have dropped to zero, and then reload the table - note: your code should now provide 1 for the number of rows and configure the cell for row 0 to display "No files" when calling the tableview delegate methods.
 
 - (void) tableView: (UITableView *) tableView commitEditingStyle: (UITableViewCellEditingStyle) editingStyle forRowAtIndexPath: (NSIndexPath *) indexPath {

     if (editingStyle == UITableViewCellEditingStyleDelete) {
         // First delete the row from the data source
         [self deleteTableData: indexPath.row];  // method that deletes data from 
         if ([self.tableDataArray count]! = 0) {
             [tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath] withRowAnimation: UITableViewRowAnimationFade];
         } else {
             [self.tableView reloadData];
         }

     }   
 }
+3
source

I have another solution for this (there seem to be many ways to do this). I found that other affordable solutions do not suit my needs, so I came up with this.

This method is very simple and good for those of you who want to use two (or more) placeholder cells, do not want to deal with your data model or several table views, and especially - you need cells to be deleted and inserted into your table. It also allows a very nice in-in transition for showing and hiding the placeholder.

Sounds great? Good! Let's look at the main point:

The real problem with most cellplace cell solutions is that they usually fail when you get around to allowing editing - deleting and pasting - on your table. This, or you should start messing around with code that handles editing, which can make everything more confusing. The problem here usually arises with the return of inconsistent values โ€‹โ€‹in the numberOfRowsInSection method. Usually in a table view, a problem arises, say, deleting a cell in a table in which one cell remains, and still one cell remains after deletion (or vice versa with insertion).

A simple solution? We always have a consistent number of cells โ€” the number of records in our data source, plus one for the placeholder. A placeholder is always present and simply shows or hides its contents based on whether it should technically be there.

Despite the long record, the implementation is actually very simple. Let's start:

1. Set up prototype placeholder cells: It's pretty simple. Set up a prototype of the cell (s) in the storyboard for your placeholder (s). In my case, I use two: one to display โ€œLoading ...โ€, while the table receives its data from the server, and the other to display โ€œTap + higher to add an itemโ€, if in fact there is nothing in the table .

Customize your cells visually as you like (I just used the Subtitle cell style and put my placeholder text in the subtitle label. Remember to remove the other label text if you do). Be sure to assign a reuse identifier and set the selection style to None.

2. Set the delegate method numberOfRowsInSection : This method will now do two main things: First, you need to return the number of rows in the data source (plus one for our placeholder), and secondly, show / hide our placeholder text if necessary. This is a good place to initiate this, since this method is called every time a cell is deleted or inserted (actually twice, once before editing and once after). To avoid starting the animation every time the method is called, we will use BOOL placeholderIsHidden to track the current status of the current placeholder. We will also execute our switch after a short delay to start the cell editing animation. Add this code to your class:

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { int count = [self.dataSource count]; // Hide/Show placeholder cell if (count == 0) { // Placeholder should be shown if (self.placeholderIsHidden) { [self performSelector:@selector(animatePlaceholderCellChangeForIndexPath:) withObject:[NSIndexPath indexPathForRow:count inSection:0] afterDelay:0.1]; } } else { // Placeholder should be hidden if (!self.placeholderIsHidden) { [self performSelector:@selector(animatePlaceholderCellChangeForIndexPath:) withObject:[NSIndexPath indexPathForRow:count inSection:0] afterDelay:0.1]; } } return count + 1; } 

Good! Now add our animatePlaceholderCellChange method:

 - (void)animatePlaceholderCellChangeForIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:0.8]; [UIView setAnimationDelegate:self]; [UIView setAnimationBeginsFromCurrentState:YES]; if (indexPath.row == 0) { cell.detailTextLabel.hidden = NO; self.placeholderIsHidden = NO; } else { cell.detailTextLabel.hidden = YES; self.placeholderIsHidden = YES; } [UIView commitAnimations]; } 

This method uses an animation block to smooth the transition between showing and hiding the placeholder (and making it suitable with cell editing animations).

Secondly, we need to configure our cellForRowAtIndexPath method to return the correct cells.

3. Setting cellForRowAtIndexPath : This is pretty simple. Put this code in your method after the declaration of the prototype identifiers and before you perform the usual cell setup:

 // Add a placeholder cell while waiting on table data int nodeCount = [self.dataSource count]; if (indexPath.row == nodeCount) { // Place the appropriate type of placeholder cell in the first row if (self.isLoading) { return [tableView dequeueReusableCellWithIdentifier:LoadingCellIdentifier]; } else { return [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier]; } } 

Lastly, let us customize our BOOL properties to keep track of whether data is loading, and whether the placeholder is hidden or not.

4. Configuring isLoading and placeholderIsHidden :

First add two declarations to your .h file:

 @property (assign, nonatomic) BOOL isLoading; @property (assign, nonatomic) BOOL placeholderIsHidden; 

Now set their initial values โ€‹โ€‹to your viewDidLoad method:

 self.isLoading = YES; self.placeholderIsHidden = NO; 

What is this for the placeholderIsHidden property! As for the isLoading property, you want to set it to YES anywhere where your code starts downloading data from your server, and NO where this operation ends. In my case, it was pretty simple, as I use the callback operation to complete the load operation.

What is it! Run your code and look at the neat, seamlessly animated and editable placeholder cell!

Hope this helps someone!

EDIT: Another important thing! It is important to note that you need to do something else with your code in order to avoid any unpleasant errors during crashes: go through your code and wherever you access elements from your data source (usually using objectAtIndex: , make sure that you never try to pull an item that does not exist.

This can be a problem in a few rare cases where, for example, you have code that accesses the elements in the data source for all visible rows on the screen (since the placeholder may be visible, you may have an indexPath that is not related to one element of your data source) . (In my case, it was a special bit of code that started loading images into all visible lines as soon as the table was scrolled). The solution to this is quite simple: wherever you access the elements from your data source, enter an if statement around the code as follows:

 int count = [self.dataSource count]; if (indexPath.row != count) { // Code that accesses the element // ie: MyDataItem *dataItem = [self.dataSource objectAtIndex:indexPath.row]; } 

Good luck

+3
source

The easiest way Absolute is to actually have two UITableViews

eg.

 UITableView * mainTable; UITableView * placeholderTable; 

Then you do something in your delegate like

 tableView:(UITableView*)tableview cellForRowAtIndexPath:(NSIndexPath*)indexPath { if (tableview == placeholderTable){ // code for rendering placeholder cell } else { // code for rendering actual table content } } 

Whenever you insert / delete objects or initialize a view, check the empty state and hide / hide your tables:

 // something changed the number of cells if (tableIsEmpty){ mainTable.hidden = YES; placeholderTable.hidden = NO; } else { mainTable.hidden = NO; placeholderTable.hidden = YES; } 

Doing this in this way will save you TONE from grief; especially when working with Core Data NSFetchedResultsController and table view animations.

+2
source

Source: https://habr.com/ru/post/1334300/


All Articles