UIKitCatalog/MenuTableViewController.swift
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
A `UITableViewController` that, when subclassed, is used as the master view of `MenuSplitViewController` instances. Each subclass specifies a mapping of segue names to perform for each static cell in its storyboard instance. |
*/ |
import UIKit |
class MenuTableViewController: UITableViewController { |
// MARK: Properties |
/** |
A matrix of segue identifiers that should be performed when a cell |
becomes focused. |
Overridden by subclasses. |
*/ |
var segueIdentifierMap: [[String]] { |
return [[]] |
} |
private var lastPerformedSegueIdentifier: String? |
private let delayedSeguesOperationQueue = OperationQueue() |
private static let performSegueDelay: TimeInterval = 0.1 |
// MARK: UIViewController |
override func viewDidLoad() { |
super.viewDidLoad() |
/* |
Set `remembersLastFocusedIndexPath` to `true` to ensure the same row |
becomes focused whenever focus is returned to the table view. |
*/ |
tableView.remembersLastFocusedIndexPath = true |
/* |
Adjust the layout margins of the `tableView` to add a horizontal inset |
to the cells. This will allow for overscan on older TVs and space for |
the focus effect. |
*/ |
tableView.layoutMargins.left = 90 |
tableView.layoutMargins.right = 20 |
} |
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { |
// Check if the requested segue is a segue contained in our mapping. |
guard segueIdentifierMap.contains(where: { $0.contains(identifier) }) else { return true } |
// Don't perform the segue if it's the same as the last performed segue. |
return identifier != lastPerformedSegueIdentifier |
} |
// MARK: UITableViewDelegate |
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { |
guard let menuSplitViewController = splitViewController as? MenuSplitViewController else { return } |
/* |
Ask the containing `MenuSplitViewController` to move the focus to the |
current detail view controller. |
*/ |
menuSplitViewController.updateFocusToDetailViewController() |
} |
override func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { |
// Check that the next focus view is a child of the table view. |
guard let nextFocusedView = context.nextFocusedView, nextFocusedView.isDescendant(of: tableView) else { return } |
guard let indexPath = context.nextFocusedIndexPath else { return } |
// Cancel any previously queued segues. |
delayedSeguesOperationQueue.cancelAllOperations() |
// Create a `BlockOperation` to perform the detail segue after a delay. |
let performSegueOperation = BlockOperation() |
let segueIdentifier = segueIdentifierMap[indexPath.section][indexPath.row] |
performSegueOperation.addExecutionBlock { [weak self, unowned performSegueOperation] in |
// Pause the block so the segue isn't immediately performed. |
Thread.sleep(forTimeInterval: MenuTableViewController.performSegueDelay) |
/* |
Check that the operation wasn't cancelled and that the segue identifier |
is different to the last performed segue identifier. |
*/ |
guard !performSegueOperation.isCancelled && segueIdentifier != self?.lastPerformedSegueIdentifier else { return } |
OperationQueue.main.addOperation { |
// Perform the segue to show the detail view controller. |
self?.performSegue(withIdentifier: segueIdentifier, sender: nextFocusedView) |
// Record the last performed segue identifier. |
self?.lastPerformedSegueIdentifier = segueIdentifier |
/* |
Select the focused cell so that the table view visibly reflects |
which detail view is being shown. |
*/ |
self?.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none) |
} |
} |
delayedSeguesOperationQueue.addOperation(performSegueOperation) |
} |
} |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-02-02