Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>I've got another solution to this (it seems like there are many ways to do this). I found the other solutions available didn't suit my needs, so I came up with this.</p> <p><strong>This method is very simple, and good for those of you that want to use two (or more) placeholder cells, don't want to mess around with your data model or multiple table views, and <em>particularly</em> - need to allow cells to be deleted and inserted in your table. It also allows for a very nice in-and-out transition for showing and hiding the placeholder.</strong></p> <p>Sounds great? Good! Let's look at the basic gist of it:</p> <p>The real issue with most of the placeholder cell solutions out there is that they will typically fail when you get around to allowing editing - deletions and insertions - on your table. That, or you have to start messing around with the code that handles editing, which can make everything more confusing. The problem here typically arises with returning inconsistent values in the <code>numberOfRowsInSection</code>method. The tableview usually takes issue with say, deleting a cell in a table that has one cell left, and still having one cell left after deletion (or vice-versa with inserting).</p> <p>The simple solution? We always have a consistent number of cells - the number of entries in our data source, plus one for the placeholder. The placeholder cell is always there, and just shows or hides its content based on whether it's <em>technically</em> supposed to be there.</p> <p>Despite the long write-up, implementing this is actually very simple. Let's get started:</p> <p><strong>1. Setup your placeholder cell prototypes:</strong> This is fairly straightforward. Set up the prototype cell(s) in your storyboard for your placeholder(s). In my case I'm using two: one to display "Loading..." while the table is getting its data from a server, and one to display "Tap + above to add an item" for when there actually is nothing in the table.</p> <p>Setup your cells visually however you like (I just used the Subtitle cell style, and put my placeholder text in the subtitle label. Don't forget to delete the other label's text if you do this). Make sure to assign a reuse-identifier, and set the selection style to 'None'.</p> <p><strong>2. Setup the <code>numberOfRowsInSection</code> delegate method:</strong> This method will now do two basic things: First is to return the number of rows in the data source (plus one for our placeholder), and second is to show/hide our placeholder's text as necessary. This is a good place to initiate this, as this method is called every time a cell is deleted or inserted (twice actually; once before editing and once after). To avoid running our animations every time the method is called, we'll use a BOOL <code>placeholderIsHidden</code>to keep track of our placeholder's current status. We'll also perform our switch after a short delay, to allow for the cell editing animation to get started. Add this code to your class:</p> <pre><code>- (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; } </code></pre> <p>That's good to go! Now let's add our <code>animatePlaceholderCellChange</code> method:</p> <pre><code>- (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]; } </code></pre> <p>This method takes advantage of an animation block, to smooth over the transition between showing and hiding the placeholder (and make it fit well with the cell editing animation).</p> <p>Second to last, we need to setup our <code>cellForRowAtIndexPath</code> method to return the correct cells.</p> <p><strong>3. Setup <code>cellForRowAtIndexPath</code>:</strong> This is fairly simple. Place this code into your method, after your prototype cell identifier declarations, and before you do your normal cell setup:</p> <pre><code>// 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]; } } </code></pre> <p>Finally, let's setup our BOOL properties for tracking whether the data is loading, and whether the placeholder is hidden or not.</p> <p><strong>4. Setup <code>isLoading</code> and <code>placeholderIsHidden</code>:</strong></p> <p>First, add the two declarations to your class's .h file:</p> <pre><code>@property (assign, nonatomic) BOOL isLoading; @property (assign, nonatomic) BOOL placeholderIsHidden; </code></pre> <p>Now, setup their initial values in your <code>viewDidLoad</code> method:</p> <pre><code>self.isLoading = YES; self.placeholderIsHidden = NO; </code></pre> <p>That's it for the <code>placeholderIsHidden</code> property! As for the <code>isLoading</code> property, you'll want to set it to <code>YES</code> anywhere your code starts loading data from your server, and <code>NO</code> wherever that operation completes. In my case it was fairly simple as I'm using an operation with callbacks for when the loading operation is complete.</p> <p>That's it! Run your code, and see the neat, smoothly animated, and editing-safe placeholder cell!</p> <p>Hope this helps someone!</p> <p><strong>EDIT: One more important thing!</strong> It's important to note that there is <em>one</em> more thing you should do with your code, so as to avoid any nasty uncalled for crashes: go through your code, and anywhere you access elements from your data source (typically using <code>objectAtIndex:</code>), make sure you never try and pull an element that doesn't exist. </p> <p>This could be an issue in a few rare cases, where for instance you have code that accesses the elements in the data source for all the visible rows on screen <strong>(since the placeholder could be visible, you may have an indexPath that doesn't relate to any element in your data source)</strong>. (In my case this was a particular bit of code that started image downloads on all visible rows, once the table view is done scrolling). Solving this is pretty easy: anywhere you access elements from your data source, put an if-statement around the code like this:</p> <pre><code>int count = [self.dataSource count]; if (indexPath.row != count) { // Code that accesses the element // ie: MyDataItem *dataItem = [self.dataSource objectAtIndex:indexPath.row]; } </code></pre> <p>Good luck!</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload