神刀安全网

Smarter Animated Row Deselection on iOS

It’s always a good idea to give your users context: at a glance, they should be able to figure out where they are, how they got there, and how to get back where they were before. One subtle but important way to give these cues is with animated deselection.

Smarter Animated Row Deselection on iOS

If you’d like, download the sample code for this post and follow along at home. This post refers to UITableViewController , but it also works with plain table and collection views.

Default Deselection

Here we see an app that uses UITableViewController to show a list of items. When you tap a row, it gets a gray selection highlight. Also called “pressed states,” these are a great way to give immediate, tactile feedback that app app is working, so don’t forget to add them! If they’re missing, and the app doesn’t respond instantaneously to every tap, your users will either think that the app is broken, or worse, that they did something wrong.

Pay attention to what happens when you tap the Back button. Notice that, as the original list moves back onto the screen, the tapped row still has the gray highlight, and it fades out over the duration of the animated transition. This is subtle, and most users probably never notice it, but it provides an important visual cue as to where you just came from. For example, if you’re going down a list and viewing details on each item, that un-highlight animation helps you avoid losing your place and tapping the same item twice.

If you’re using UITableViewController , you get this behavior “for free”; that is, it will automatically deselect the selected row during a “back” navigation event. However, in some cases you won’t get automatic deselection, and even when you do, it doesn’t work correctly with the interactive, percent-driven animations that were introduced in iOS 7, including the one built into UINavigationController . Watch what happens when I interactively dismiss this screen by sliding my thumb from the left side of the screen, a common gesture among users of iPhone 6 and iPhone 6 Plus devices:

Smarter Animated Row Deselection on iOS

The resulting behavior is at best suboptimal, and at worst downright broken. When I use an interactive gesture to pop the navigation stack, the row either waits until the animation is done to un-highlight, or it just gets stuck and never un-highlights. It would be better if the row faded out smoothly as a function of the transition progress.

Naïve Deselection

Fortunately, animations triggered from within UIViewController ’s viewWillAppear method will pick up the surrounding animation context’s properties. All we need to do is ask the table view to deselect its selected rows with animated: true , and the deselection animation will magically move forward and backward with the progress of the interactive gesture. The code in the master view controller (the one that contains the table view) looks like this:

class MasterViewController:UITableViewController {     override func viewWillAppear(animated: Bool) {         super.viewWillAppear(animated)         tableView.indexPathsForSelectedRows?.forEach {             tableView.deselectRowAtIndexPath($0, animated: true)         }     } } 

Don’t worry if your Swift is a little rusty. Here’s what it says: for each of the index paths in the array of the table view’s selected index paths, ask the table view to deselect that index path with animation enabled. The $0 refers to the argument passed into the forEach closure, which, in this case, is an NSIndexPath .

Here’s how it looks:

Smarter Animated Row Deselection on iOS

Pretty cool! The cell deselection animation is a function of how far the detail view controller has slid off the screen. But hang on, what’s happening when I start an interactive dismiss animation, and then change my mind and cancel it? viewWillAppear is still getting called, so the cells are being deselected, but if I cancel the transition, the cells aren’t reselected.

Smart Deselection

What we need is a way to reselect the cells if the dismissal is canceled. Fortunately, there’s a way to do what we need using the view controller’s transition coordinator. I’m implementing it here as an extension on UIViewController so that it’s easy to call without cluttering up your viewWillAppear method:

override func viewWillAppear(animated: Bool) {     super.viewWillAppear(animated)     rz_smoothlyDeselectRows(tableView: tableView) }   extension UIViewController {     func rz_smoothlyDeselectRows(tableViewtableView: UITableView?) {         // Get the initially selected index paths, if any         let selectedIndexPaths = tableView?.indexPathsForSelectedRows ?? []           // Grab the transition coordinator responsible for the current transition         if let coordinator = transitionCoordinator() {             // Animate alongside the master view controller's view             coordinator.animateAlongsideTransitionInView(parentViewController?.view, animation: { contextin                 // Deselect the cells, with animations enabled if this is an animated transition                 selectedIndexPaths.forEach {                     tableView?.deselectRowAtIndexPath($0, animated: context.isAnimated())                 }             }, completion: { contextin                 // If the transition was cancel, reselect the rows that were selected before,                 // so they are still selected the next time the same animation is triggered                 if context.isCancelled() {                     selectedIndexPaths.forEach {                         tableView?.selectRowAtIndexPath($0, animated: false, scrollPosition: .None)                     }                 }             })         }         else { // If this isn't a transition coordinator, just deselect the rows without animating             selectedIndexPaths.forEach {                 tableView?.deselectRowAtIndexPath($0, animated: false)             }         }     } } 

Finally, deselection happiness:

Smarter Animated Row Deselection on iOS

When the transition is canceled, the cells are reselected so they can interactively unhighlight all over again during the next attempt at a transition.

Details like this are fiddly, and invisible to most people if you get them right. But if they’re missing, users will notice, or be confused, or both. Luckily, now you don’t have to work hard to sweat this particular detail. You can see the commented sample code here , but the easiest way to add it to your own projects is to grab it from the Raizlabs Swift grab-bag, Swiftilities . There is also a gist which contains both Swift and Objective-C versions of this code, if you’d prefer to just download it and drop it into your project. Thanks to Brad Smith for helping me figure this stuff out.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Smarter Animated Row Deselection on iOS

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址