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.
Related
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.
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!)
})
}
}
I made a view controller with a UISearchController and a UITableView . There are two different kind of search you can select from the search scope buttons : groups and people. Both searches work and show results on the table. However, if you click on each cell they should direct you to different dynamic pages (a dynamic group page or a dynamic person profile page). The one for groups works, while the one for profiles doesn't. Meaning whenever I click on the a person cell from the results that I got, nothing happens and I get the following warning printed on the console :
Warning: Attempt to present <MyProject.profileView: 0x13e9df000> on <MyProject.SearchPage: 0x142a1d8f0> which is already presenting <UISearchController: 0x142a1f7c0>
If you have any idea why this could be happening it'd be really appreciated if you could let me know.
EDIT : Here's the function that should link the cell to the different view controllers :
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if self.searchForGroups {
let detailCell:PFObject = self.filteredGroups.objectAtIndex(indexPath.row) as! PFObject
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("DynamicCellView") as! DynamicCellView
vc.GroupId = detailCell.objectId!
self.presentViewController(vc, animated: false, completion: nil)
}else{
//Link to use profile
let user = self.peopleResults[indexPath.row]
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("ProfileView") as! profileView
vc.profileId = user.objectId!
self.presentViewController(vc, animated: true, completion: nil)
}
}
I was having the same warning and this fixed it for me. You need to stop presenting the search controller so you can present other controller while you are leaving the view.
override func viewDidDisappear(_ animated: Bool) {
if SearchController.isActive == true {
SearchController.isActive = false
}
}
I was figuring the same original issue and none of this resolved it for me.
Actually you just have to dismiss the UISearchController as it is said because it is already presenting to the current view.
So, when you want to launch your action, you just have to call this :
if searchController.isActive {
self.searchController.dismiss(animated: false) {
// Do what you want here like perform segue or present
}
}
Hope this will help!
I hit the same error while trying to perform a segue when tapping on a search result. This isn't an ideal workaround but dismissing the searchController before performing the segue fixed it for me:
self.searchController.dismiss(animated: false) {
self.performSegue(withIdentifier: "<YOUR SEGUE IDENTIFIER>", sender: cell)
}
I'm not sure if the above answers worked properly in previous version but in swift 5, calling dismiss will cause the segue to trigger properly but the search bar will remain active and when they dismiss the triggered segue (come back to the search page) the search bar will look active but the results will not be.
Also dismissing from viewDidDisappear() did not work properly. Here's how I did it.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Do some stuff here
if searchController.isActive{
searchController.isActive = false
}
performSegue(withIdentifier: "<yourSegueIdentifierHere>", sender: nil)
}
Dismissing UISearchController (proposed in this thread in dozen different ways) is a working solution. One more workaround I've found is just having
definesPresentationContext = true
in viewDidLoad() of a View Controller having UISearchController. This workaround is better in a way the once you navigate back, UISearchController is still showing the search results.
Without the code is kind of difficult to help you. That error may be happening because you are breaking the view controller hierarchy.
The message is saying that you have 3 view controllers involved:
SearchPage is presenting UISearchController
profileView is not yet presented but should be presented on UISearchController or should replace it (For that UISearchController should be dismissed first)
Take in mind that a view controller can present only 1 view controller at the time but it can have several child view controllers (such as a navigation controller).
For more information you can check: https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/TheViewControllerHierarchy.html#//apple_ref/doc/uid/TP40007457-CH33-SW1
Just as a comment, its a good coding practice to start your class name with a uppercase letter ('ProfileView' instead of 'profileView')
I have simple app. My tableViews contains some words; I post data in post screen and I want to get back to the first tableView screen. I have use a segue for this. The problem is that each time after posting, clicking the post button opens a new tableView over the post screen. I want to return to the original.
Table View Screen -> Post Screen -> New Table View Screen
But I want
Table Vievscreen <-> Post Screen
I had the same issue.It is resolved now. Please let me know if this helps.
I already have a segue linked from table view cell to another view with name DynamicSuperView
func tableView(_tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
//Remove the below line from this function as it will perform segue.
//performSegue(withIdentifier: "DynamicSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
// This will actually perform your segue
var DestViewController = segue.destination as! DynamicSuperView
let selectedRow = tableView.indexPathForSelectedRow?.row
DestViewController.labelText = names[selectedRow!]
}
Hope this helps to somebody.
We need a LOT more information to what is provided. However this may get you started in the right way.
If you are using a navigation controller, put this in your posting view code:
navigationController!.popViewControllerAnimated(true)
If you are NOT using navigationController but instead showing the view modally, then use the following in your posting view code:
dismissViewControllerAnimated(true, completion: nil)
Most segues create a new instance of a view controller and display it on top of existing view controllers.
If you are adding a view controller to a navigation stack with a push, you want to do a pop as described in JAL's comment.
If your segue is modal, you want to do a dismissViewController:animated:
You could also set up an unwind segue, although that might be overkill for this simple case.
I'm getting the same issue here, but I realize that I just did wrong on control+click, I dragged from cell, the correct must be from tableview
I hope this help some one
This seems to be a common problem, but none of the many solutions I've tried have seemed to work (or I'm not executing them properly).
I've got an image on FirstImageVC, I push a button to bring up a new view collection view controller with some custom images, the user selects one, and I want to send that image back to the FirstImageVC to overlay the original image, sort of like a sticker.
I just can't get it to execute a segue of any kind on selecting an image. Here's what I'm sort of working with in the second view controller. And it seems I may need to be adding something to the original VC, too, no?
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
prepareForSegue("backToFirstSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "backToFirstSegue"{
let vc = (segue.destinationViewController as! FirstImageVC)
vc.Delegate = self //Include this line
vc.chosenGhostPhoto = bgGhostImage?.image
}
}
EDIT 1: Here's what I did to get the unwind to work, though it's not carrying back the image selected from the collection view, which needs to be called chosenGhostPhoto.
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("unwindToFirstSegue", sender: self)
}
This is how you can do this:
Step 1: Add a delegate variable on your second view controller. Please name it delegate and not Delegate - owing to naming convention.
Step 2: Define a protocol and add functions that you want your second view controller delegate to perform. Say func selectedImage(image : UIImage)
Step 3: While pushing second view from first view controller, set your first view controller as delegate of second view controller. Something like this:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "secondSegue"{
let vc = (segue.destinationViewController as! SecondImageVC)
vc.delegate = self
}
}
Step 4: In your second view controller once image is selected call delegate of second view controller. Below function needs to be triggered on tap on the image on second view controller.
fun imageSelected {
self.delegate.selectedImage(bgGhostImage?.image)
}
Step 5: Implement selectedImage in your first view controller and use the passed image.
An unwind segue (sometimes called exit segue) can be used to navigate back through push, modal or popover segues (as if you popped the navigation item from the navigation bar, closed the popover or dismissed the modally presented view controller). On top of that you can actually unwind through not only one but a series of push/modal/popover segues, e.g. "go back" multiple steps in your navigation hierarchy with a single unwind action.
When you perform an unwind segue, you need to specify an action, which is an action method of the view controller you want to unwind.
For your reference this is quit similar question and might help in understanding unwind segue