Reputation: 23223
Setup: Create a boilerplate iOS Master-Detail app in Xcode and run it. Everytime you click on a master item it re-creates the detailVC and configures it to display the new data.
Premise: Often this is what I want, but other times it does little more than change the text in a single label. Doesn't it make more sense to re-use the existing DetailVC? (at least in this case)
So what's the best way to do this?
Contemplation: When I look at the boilerplate code I see the MasterVC creates class scoped var for the detailVC.
var detailViewController: DetailViewController? = nil
It sets this value in viewDidLoad
, but it then never uses it for anything. Huh? In prepare(for:sender:)
we get this code
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
which creates a new instance of the DetailViewController
. If you put a break point in after it's created and compare it to the class var detailViewController
you can see they are different.
The UIStoryboardSegue
is creating my new DetailViewController
which can easily be confirmed by looking at the documentation.
You do not create segue objects directly. Instead, the storyboard runtime creates them when it must perform a segue between two view controllers.
I could remove the segue on row select and manually call the method on my detailVC and that's probably the easiest, but segues are so purty and visual in IB. I could could probably create a custom segue. What else could I do and is there a clear "best" way?
Upvotes: 0
Views: 338
Reputation: 30746
First remove the var detailViewController
that is a remnant from when the template was using UINavigationController
and won't work with the UISplitViewController
because if a new master is init when collapsed, say after a selection in a root VC then it isn't possible to find the previous detail. iOS 14 added a new method to easily find the previous detail even if the split is currently collapsed, viewController(for:) and you could use it like this:
// only perform when collapsed, otherwise find the detail and update it's item.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier != "showDetail" { return true }
guard let indexPath = tableView.indexPathForSelectedRow else { return true }
guard let svc = splitViewController else { return true }
if svc.isCollapsed { return true }
let secondaryViewController = svc.viewController(for: .secondary)
guard let secondaryAsNavController = secondaryViewController as? UINavigationController,
let topAsDetailController = secondaryAsNavController.topViewController as? DetailViewController else { return true }
let object = fetchedResultsController.object(at: indexPath)
topAsDetailController.detailItem = object
return false // cancels the segue
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = fetchedResultsController.object(at: indexPath)
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Upvotes: 1
Reputation: 23223
In case someone else has this question, here is my solution until someone comes up with something better.
Go to Interface Builder and remove the showDetail
segue which goes from the master tableview to the detailVC. Then go into the MasterViewController class and remove the prepare(for:sender:)
function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
if let indexPath = tableView.indexPathForSelectedRow {
let object = objects[indexPath.row] as! NSDate
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.detailItem = object
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
Create a new function in Table View area (this new function is part of UITableViewDelegate
)
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
detailViewController?.detailItem = object
}
Upvotes: 0