How to properly use didSelectRowAt in splitViewController on compact device in swift? - ios

I'm using a splitViewController to display a master view and a detail view.
When I tap on a row, the detail view updates correctly.
Then, when I'm in portrait view, I collapse the splitview detail view, so that that master list items are shown as follows:
And when I tap on a row, I correctly move to the detail view, as shown:
The problem I'm having is that if I rotate the device in the detail view shown above, while I'm in the detail view, the rotation correctly goes back to the splitView, however, now when I select a row, the delegate method does not update the detail view. It only seems to work if I start out in the splitView and stay in that view, or if I start out in the collapsed view and stay in that. If I rotate, then the delegate method does not seem to work.
I found a prior post, which shows how to use the delegate method to update the detail view using objective C code, using the didSelectRow function. I tried to duplicate this code with the following swift code:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let navigationVC = UINavigationController()
var detailVC = TestsDetailAdvertVC()
if let tests = controller.fetchedObjects, tests.count > 0 {
//if there is, keep track of the test which is selected
selectedTest = tests[indexPath.row]
if let isCollapsed = splitViewController?.isCollapsed {
if isCollapsed {
//solves problem of navigating to the detail view in compact view
// on the iPhone (compact) the split view controller is collapsed
// therefore we need to create the navigation controller and detail controller
detailVC = self.storyboard!.instantiateViewController(withIdentifier: "detailVC") as! TestsDetailAdvertVC
navigationVC.setViewControllers([detailVC], animated: false)
self.splitViewController?.showDetailViewController(detailVC, sender: self)
detailVC.testToEdit = selectedTest
} else {
// if the split view controller shows the detail view already there is no need to create the controllers
// so we just pass the correct test using the delegate
// if the test variable is set, then it calls the showDetail function
delegate?.testToEdit = selectedTest
}
}
}
}
I think that somehow when the one or the other method is used to update the detail view it works, but then when it switches back and forth, it stops working. I wonder if anyone has solved this issue using swift code who could point me to an example.
Note: After some additional searching, I realized that there are a few of delegate methods for the splitViewController, including:
func primaryViewControllerForExpandingSplitViewController:
and
func primaryViewControllerForCollapsingSplitViewController:
and
splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
I've been fiddling around with these methods, but so far haven't been able to get them to work, and I haven't found any posts that show examples of how they are used.
Thanks.

I figured out how to make the detail view update properly, using an answer from a prior post at:
In UISplitViewController, can't make showDetailViewController:sender: push onto detail navigationController
my code to solve the problem is updated using swift code:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var detail = UINavigationController()
var testVC = TestsDetailAdvertVC()
if let tests = controller.fetchedObjects, tests.count > 0 {
//if there is, keep track of the test which is selected
selectedTest = tests[indexPath.row]
if let isCollapsed = splitViewController?.isCollapsed {
if isCollapsed {
//in collapsed view, the correct detail view controller is not
//yet substantiated, so we need to substantiate it
testVC = self.storyboard?.instantiateViewController(withIdentifier: "detailVC") as! TestsDetailAdvertVC
detail.setViewControllers([testVC], animated: true)
testVC.testToEdit = selectedTest
} else {
//in expanded view, the correct view controller needs
//to be identified, using the appropriate view controller for
//the splitview controller
let vc = self.splitViewController?.viewControllers[1]
//which is a navigation controller
if vc is UINavigationController {
detail = vc as! UINavigationController
//which we then use to identify the correct detail view
testVC = detail.viewControllers.first as! TestsDetailAdvertVC
testVC.testToEdit = selectedTest
}
}
}
}
self.splitViewController?.showDetailViewController(detail, sender: self)
}
The key solution is that on the collapsed splitviewcontroller, the detail view has to be instantiated form the storyboard. However, on the expanded splitviewcontroller, the detail view has to come from the expanded navigation controller. Then when I rotate the correct detail view controller updates correctly.

Related

Load ViewController as child view in main view from other child view

I´m coding an application using swift3/xcode 8. My application have a central view controller, called MenuViewController, which is responsible for the side menu.
Inside this one, i load the other views as child views, so in this way I can use the side menu in all other views.
But I´m in doubt now: In a specific view controller I have a table view, which have a method to perform an action on selecting an item. This action calls other
view and pass a parameter.
So, how do I load this view in the BaseViewController with this parameter? Is it possible?
Please let me know if it is not clear....
Thank you!
Update 1:
I´m using a solution I found interesting:
Cocoacasts
Basically I have this code on MenuViewController:
Declare the view to be loaded in MenuViewController:
lazy var filamentoViewController: FilamentoViewController = {
let storyboard = storyboard.instantiateViewController(withIdentifier: "FilamentoViewController") as! FilamentoViewController
self.addViewControllerAsChildViewController(childViewController: viewController)
return viewController
}()
Adds the viewcontroller as child in MenuViewController:
private fun addViewControllerAsChildViewController(childViewController: UIViewController)
{
addChildViewController(childViewController)
view.addSubView(childViewController.view)
childViewController.view.frame = view.bounds
childViewController.view.autoResizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParentViewController: self)
}
Code in FilamentoViewController which calls other viewcontroller:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath)
{
let editFilamentoViewController:EditFilamentoViewController = EditFilamentoViewController()
editFilamentoViewController.cod = indexPath.row
self.presentViewController(editFilamentoViewController, animated: true, completion: nil)
}
The code
self.presentViewController(editFilamentoViewController, animated: true, completion: nil)
is the one I want do load into MenuViewController.
Update 2:
MenuViewController is a CentreViewController. I load the other views in the center and the menu comes from left.
There is a built-in controller for your kind of requirement(i.e UISplitViewController) , Have you considered this?
Apple documentation
Sample Tutorial
And there are also slide-out menu libraries.
slide out menu

Error when using unwind segue with UISearchController

I have a Navigation Controller with a UITableViewController.
When a user selects a cell on the TableViewController, it pushes to a new View Controller with a Table View inside. The user then selects a cell and the data gets passed back via an unwind segue.
The problem is I get this error when using a search bar before selecting the cell. Here is what the console says:
popToViewController:transition: called on <UINavigationController 0x7fc8ab856e00> while an existing transition or presentation is occurring; the navigation stack will not be updated.
Code from View Controller I'm unwinding from:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cell = tableView.cellForRow(at: indexPath) as! MonsterSpriteCell
let monster = monsters[indexPath.row]
selectedMonster = monster
if isTeamBuilding {
// **ERROR OCCURS HERE**
performSegue(withIdentifier: "saveToTeamBuilderTableVC", sender: cell)
} else {
performSegue(withIdentifier: "showMonsterDetail", sender: self)
}
}
Here is the link the the project. The View Controller I'm unwinding from is Browse View Controller. The View Controller I'm unwinding to is TeamBuilderViewController
https://github.com/emanleet/monsterpedia
EDIT: I think it might be relevant to note that the segue unwinds to TeamBuilderTableViewController, which is a View Controller that is inside a container as a part of another view controller. Does anyone know if this might be why my unwind segue isn't working?
Two step thing, first dismiss the search controller presented view controller, then, do your thing.
yourSearchController.dismiss(animated: true, completion: {
self.performSegue(withIdentifier: "yourUnwindSegue", sender: self)
})
The SearchController is presenting an empty view controller
If you print the presentedViewController in didSelectRow.. when the search is active you will see a view controller.
that means you’re trying to perform segue from under the current presentation. You should dismiss the SearchController before performing any segues or presentations.
Also in this case you don’t need a SearchController since you’re only using the SearchBar for filtering.
Instead, put a SearchBar in the tableViewHeaderView and use its delegate to do the filtering and instead of checking if isActive to access the complete list vs. the filtered results, just put the whole array in the filter when the text is cleared and always access the filtered results.

How do I dismiss a UISearchController view?

Context: There's a map view with a UISearchController implemented. The search controller displays its results in a different table view controller
I found this answer which explains how to dismiss the search controller within the same view. However, my results page is a different view than its origin page. I have
let resultsPage = ResultsTableViewController()
let searchController = UISearchController(searchResultsController: resultsPage)
So now when the search controller gets results, it displays them on the table view controller. When the user selects one of the results, I want to dismiss this view and go back to the map view, passing their selection along. Should I do this with a segue, or is there a better programmable approach?
Did this same thing in my app. My map view controller has a search bar. When the user enters a search string, it presents a table view controller with a list of addresses that were found. The user can select an address which then dismisses the table view controller and drops a pin on the map for the selected address.
In the table view that displays, use didSelectRowAtIndexPath to dismiss the table view controller and use a protocol/delegate to pass the selected item back to the map view controller. Have your map view controller conform to this protocol. I did this exact setup and it works well.
protocol DropPinOnSelectedLocationDelegate {
func dropPinOnLocation(placemark:MKPlacemark)
}
class LocationSearchTable: UITableViewController {
...
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
self.mySearchController.searchBar.endEditing(true)
if let indexPath = tableView.indexPathForCell(cell!) {
selectedItem = matchingItems[indexPath.row].placemark
}
self.dismissViewControllerAnimated(true, completion: {
self.delegate?.dropPinOnLocation(self.selectedItem!)
})
}
}

Swift: how to detect if UISplitViewController is currently showing 1 or 2 controllers?

How can I detect if the UISplitViewController is currently just showing 1 view controller or it's in dual-pane with 2 views controllers shown side-by-side?
The split view controller reflects the actual display mode in the displayMode property:
AllVisible: The primary and secondary UIViewControllers are displayed side-by-side.
PrimaryHidden: The primary UISplitViewController is hidden.
PrimaryOverlay: The primary UISplitViewController overlays the secondary, which is partially visible.
When the isCollapsed property is true, the value of displayMode property is ignored. A collapsed split view interface contains only one view controller so the display mode is superfluous.
Resume: To find out the detailed situation on screen use isCollapsed property and (if isCollapsed = false) displayMode property.
Here is a simple case:
You are on the MasterViewController and you select a cell. Now, depending if the UISplitViewController is collapsed or not you want to either perform a segue (circled in red)
to the DetailViewController (collapsed) or update the DetailViewController (not collapsed).
In your "didSelectRowAtIndexPath" method on your MasterViewController get a reference to the UISplitViewController and choose what to do like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Reference to Split View
guard let splitView = self.splitViewController else {
return
}
//Check the collapsed property
if splitView.collapsed {
self.performSegueWithIdentifier("segueToDetail", sender: self)
}else {
//Get reference to your details navigation controller
guard let detailViewNavigationController = self.splitViewController?.viewControllers[1] as? UINavigationController else {
return
}
//Get a reference to your custom detail view controller
guard let detailController = detailViewNavigationController.viewControllers[0] as? MyCustomViewController else {
return
}
//Call your custom function to update the detail view controller
detailController.updateSomething()
}
}
If you don't want to use the "collapsed" property of the UISplitViewController you can check the number of view controllers property like this.
if splitView.viewControllers.count == 1 {
self.performSegueWithIdentifier("segueToDetail", sender: self)
}else splitView.viewControllers.count == 2 {
guard let detailViewNavigationController = self.splitViewController?.viewControllers[1] as? UINavigationController else {
return
}
guard let detailController = detailViewNavigationController.viewControllers[0] as? MyCustomViewController else {
return
}
detailController.updateSomething()
}
Another option is to set up delegation from your master view controller to your detail view controller. This will work well if you don't want to have to reach up the view controller chain like this example does. Here is a tutorial on this method. Note the "Hooking Up The Master With the Detail" section.
Just a note: I tested switching on the UISplitViewControllers "displayMode" property. This property did not give me enough info to figure out what to do. The reason is that the property is set to .AllVisible when you are in the horizontal compact mode and the horizontal expanded mode.
Last, before I go. I like the way I do it because lots of times you know you are going to need a UISplitViewController so you create a project from the template. You will notice the template comes with the segue set up. This template is great for phones but doesn't cut it for iPads and iPhone6+'s. If you drag and drop a UISplitViewController onto a story board after project creation you will notice the detail view is neither imbedded in a UINavigationController nor is there a segue from the master to the detail. Just more to set up I guess!
There is a property of UISplitViewController named 'collapsed'.

Different behavior for segue using UISearchController

I must be doing something wrong here.
I have a UITableView, and have implemented a UISearchController.
I have a Prototype cell linked to the details screen and pass the selected value in the prepareForSegue method.
For the normal view controller, all works OK, a row is selected and the details screen pushed (i.e. slide in from right).
However when there is an active search, using the UISearchController, the details screen is presented modally (i.e. slide up from bottom of screen) without the UINavigationBar (so there is no possibility to go "back")
I am not using didSelectRowAtIndexPath since I have used storyboard to push the details screen
Why is the presenting animation different when the same code in "prepareForSegue" is being correctly called in each case:
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier! == "PresentContactDetail") {
// pass person details from selected row
if let selectedRow = self.tableView.indexPathForSelectedRow()?.row {
let selectedPerson = self.visibleResults[selectedRow]
(segue.destinationViewController as ContactDetail).personRecord = selectedPerson
}
}
}
Any suggestions gratefully received.
in ViewDidLoad of UITableViewController set
definesPresentationContext = true

Resources