func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(identifier:
"PersonViewController") as? PersonViewController
vc?.names = persons[indexPath.row].emer!
vc?.lastnames = persons[indexPath.row].mbiemer!
vc?.delegate = self
PersonViewController.indexes = indexPath.row
self.navigationController?.pushViewController(vc!, animated: true)
}`
I have a situations like this:
First ViewController is a collectionView, the second is a viewcontroller which is allowed to add new Person when I tap a button and works perfectly. I have used delegates and Core Data for local memory.
Also the second ViewController has another button to edit person. When I press button a new viewController appears with extension UIAdaptivePresentationControllerDelegate. This viewcontroller consists of 2 buttons and 2 textfields. So when I want to press save button I want to go to the first viewcontroller (collectionview list) and when to press cancel to go back to the second viewcontroller.
Viewcontrollers are created with pushViewController method.
Please anyone help what should I use?
then in PersonViewController I call this inside button edit.
#objc func editCell(){
let vc = storyboard?.instantiateViewController(identifier:
"ModalPresentationViewController") as?
ModalPresentationViewController
navigationController?.pushViewController(vc!, animated: true)
}
Now the code in the las ViewController which is ModalViewController
#objc func savePerson(){
if editNameTextfield.text == "" || editlastNameTextfield.text == ""{
self.errorLbl.alpha = 1
}
else{
let vc = ViewController()
guard let textName = editNameTextfield.text else{
return
}
guard let textLastName = editlastNameTextfield.text else{
return
}
let index = PersonViewController.indexes
DispatchQueue.main.async {[self] in
if editDelegate != nil{
self.editDelegate!.editPerson(editedName: textName, editedLastname: textLastName, index: index)
}
}
// What should I call here??
}
}
You can use something like:
func popTo(_ type: AnyClass) {
guard let controllers = navigationController?.viewControllers else {return}
for controller in controllers {
if controller.classForCoder == type {
navigationController?.popToViewController(controller, animated: true)
break
}
}
}
And invoke the function depending on the condition as:
popTo(A.classForCoder())
EDIT: Above solution works only if ViewControllers B and C presented via a navigation controller and are in the same navigation stack.
Since you present the ViewController C modally, below answer should work:
Make these changes to your ViewController B that is presenting C:
class B: UIViewController, PresenterDelegate {
// some functions
func popToPrevious() {
navigationController.popViewController(animated: false)
}
#objc func editCell(){
let vc = storyboard?.instantiateViewController(identifier:
"ModalPresentationViewController") as?
ModalPresentationViewController
vc.presenterDelegate = self
present(vc, animated: true, completion: nil)
}
}
And in your ViewController C:
class C {
var presenterDelegate: PresenterDelegate? = nil
//
#objc func savePerson(){
//
//
dismiss(animated: true, completion: {
self.presenterDelegate?.popToPrevious()
})
}
}
Also add PresenterDelegate protocol to a new file or below ViewControllers B or C
protocol PresenterDelegate {
func popToPrevious()
}
Related
In my app I use .present(...) to show one View Controller on top of the other (these are the same VC). When I dismiss it (pull down from screen) I want to select some row in my first VC. For it I use UIAdaptivePresentationControllerDelegate in which I have print(...) and tableView.selectRow(...) function. Print works fine, but tableView.selectRow doesn't work. What could be the issue?
extension UIViewController {
class func loadFromStoryboard<T: UIViewController>() -> T {
let name = String(describing: T.self)
let storyboard = UIStoryboard(name: name, bundle: nil)
if let viewController = storyboard.instantiateInitialViewController() as? T {
return viewController
} else {
fatalError("Error: No initial view controller in \(name) stroryboard")
}
}
}
class MyTabBarController: UITabBarController {
let firstVC: FirstVC = FirstVC.loadFromStoryboard()
...
override func viewDidLoad() {
super.viewDidLoad()
loadViewControllers()
}
private func generateViewController(rootViewController: UIViewController, image: UIImage?, title: String) -> UIViewController {
let navigationVC = UINavigationController(rootViewController: rootViewController)
...
return navigationVC
}
private func loadViewControllers() {
viewControllers = [
generateViewController(rootViewController: firstVC, image: ..., title: "...")
]
}
...
private func openVC() {
let storyboard = UIStoryboard(name: "FirstVC", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "FirstVC") as! FirstVC
vc.presentationController?.delegate = vc
self.present(vc, animated: true, completion: nil)
}
...
}
//FirstVC
extension FirstVC: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("VC was dismissed")
self.tableView.selectRow(at: IndexPath(row: 0, section: 0), animated: true, scrollPosition: .none)
}
}
In the console I see "VC was dismissed", but row doesn't select.
P.S. I use Xcode 12.2 and iOS 14.2.
The tableView is an IBOutlet, if it's important.
You should set:
vc.presentationController?.delegate = self
Instead of:
vc.presentationController?.delegate = vc
Because you lose delegate as soon as you dismiss vc. You should listen dismissal of FirstVC on self where you present FirstVC.
If you just miss that it's ok. But if you can't understand why I can share more details :)
We have Router class to navigate the viewController to another view controller, It's working as expected but when randomly the viewControllersStack gets nil and crash occurred. We tried to use it "if let" to avoid the crash but the issue here is black screen appeared when the viewControllersStack is nil. so we have reverted it back. Can you suggest why the navigation stack is nil if the navigation is nil how to handle it?
private func PopOrPushToViewController(_ strControllerClass: String) {
//get the list of controllers in the stack
let viewControllersStack: [UIViewController] = self.navigationController!.viewControllers as [UIViewController]
var boolDidNaviagtion = false
for viewController in viewControllersStack {
if boolDidNaviagtion {
viewController.removeFromParent()
}
if String(describing: type(of: viewController)) == strControllerClass {
boolDidNaviagtion = true
self.navigationController?.popToViewController(viewController, animated: true)
}
}
if !boolDidNaviagtion {
let viewController = NavigationController.sharedInstance.storyboardViewControllerFromString(strControllerClass)!
self.navigationController!.pushViewController(viewController, animated: true)
}
}
class AddTripViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func navigate(_ sender: Any) {
popOrPushToViewController( "ListViewController")
}
}
The problem is likely your use of:
viewController.removeFromParent()
If you pop to a VC in the stack, the other VCs will be removed automatically.
Try changing your func to this:
private func PopOrPushToViewController(_ strControllerClass: String) {
// get the list of controllers in the stack
if let vcStack: [UIViewController] = self.navigationController?.viewControllers {
var didPop = false
for vc in vcStack {
// if we find the desired VC already in the navVC's stack of controllers,
// pop to it, set the didPop flag to true, and quit looking
if String(describing: type(of: vc)) == strControllerClass {
self.navigationController?.popToViewController(vc, animated: true)
didPop = true
break
}
}
// if we did NOT pop to the desired VC,
// instantiate it and push it onto the stack
if !didPop {
if let vc = NavigationController.sharedInstance.storyboardViewControllerFromString(strControllerClass) {
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
My scenario, I am having Tabbar with three viewcontroller. Here, tabbar first viewcontroller I am showing tableview. If I click the tableview cell it will show one popup present model viewcontroller. In this present popup viewcontroller I am maintaining two bar button cancel and done. If I click done It will dismiss and show tabbar main view controller. While dismiss time I need to pass some values with button flag from present popup view controller to tabbar main viewcontroller.
Here, below my dismiss popup pass viewcontroller code (VC 2)
#IBAction func apply_click(_ sender: Any) {
print("Dimiss Filter")
dismiss(animated: true, completion: {
if let navView = self.tabBar?.viewControllers?[0] as? UINavigationController {
if let secondTab = navView.viewControllers[0] as? HomeViewController {
secondTab.selectedIndexFromFirstTab = self.selectedIndex
//secondTab.item = self.item
secondTab.tfData = "YES"
}
}
self.tabBar?.selectedIndex = 0
})
}
Here, Tabbar main view controller code (receiving values) (VC 1)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("SELECTED INDEX:\(selectedIndexFromFirstTab)")
print("RESPONSE:\(tfData)")
}
I am not receiving values, how to solve this issue.
From my previous answer you need to make few changes to existing code if you want to pass values from your child view to main tab bar controller.
For that first of all you need to declare a method into your main TabBarViewController
func tabPressedWithIndex(index: Int, valueToPass: String) {
print("selected Index: \(index)")
print("Value from user: \(valueToPass)")
}
Now you need to pass that tab bar controller to your detail view with didSelectRowAt method and it will look like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.selectedIndex = indexPath.row
vc.tabBar = self.tabBarController as? TabBarViewController // Here you need to assign tab bar controller.
self.present(vc, animated: true, completion: nil)
}
Now next thing is when you click on dismiss button from detail view controller you need to add one line below self.tabBar?.selectedIndex = 1:
self.tabBar?.tabPressedWithIndex(index: 1, valueToPass: self.userTF.text!)
Now this will pass the values to main tab bar controller and method tabPressedWithIndex will call and print your data in your main tab.
Check out demo project for more info.
You can achieve it multiple ways. Using blocks/closures, protocols or if you are using RxSwift than using controlled property or using controlled events. Because I can't demonstrate everything here am gonna write protocol
Using Protocol
Step 1:
Declare a protocol in your modal view controller
#objc protocol ModalViewControllerProtocol {
func dismiss(with data: String)
}
Step 2:
ViewController that presents this ModalViewController make it to confirm the protocol
extension HomeViewController: ModalViewControllerProtocol {
func dismiss(with data: String) {
//use the data here
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
Step 3:
Declare a variable to hold delegate reference in ModalViewController
weak var delegate: ModalViewControllerProtocol? = nil
Step 4:
In your ViewCOntroller that presents the modalViewController pass self as a delegate to ModalViewController before presenting
let modalVC = //...
modalVC.delegate = self
self.present(modalVC, animated: true, completion: nil)
Finally in IBAction of ModalViewController simply call
#IBAction func apply_click(_ sender: Any) {
self.delegate?.dismiss(with: "your_data_here")
}
Using Block/Closure
Step 1:
In your modal ViewController declare a property that accepts a block/closure
var completionBlock: (((String) -> ()))? = nil
Step 2:
In your ViewController that presents this ModalViewController, pass a block before presenting it
let modalVC = //...
modalVC.completionBlock = {(data) in
debugPrint(data)
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
self.present(modalVC, animated: true, completion: nil)
Step 3:
Finally in your ModalViewController IBAction simply execute the block passed
if let block = completionBlock {
block("your data here")
}
Hope it helps
This is the solution
self.dismiss(animated: true) {
if let tabController = self.presentingViewController as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
if let secondTab = navController.viewControllers.first as? HomeViewController {
secondTab.tfData = "YES"
}
} else {
if let secondTab = tabController.selectedViewController as? HomeViewController {
secondTab.tfData = "YES"
}
}
}
}
My scenario, I am having tabbar with three viewcontroller and first tabbar viewcontroller I am showing tableview with some data. Whenever I am clicking the tableview cell I am passing the data to one special present model popupcontroller. If I dismiss popup controller I need to show directly to tabbar index 1 (I mean tabbar second viewcontroller) also need to pass the values.
Here, below code I tried
After select the tableview cell passing values to popupview controller
Popup close button click to pass the same value to tabbar index 1 (tabbar second viewcontroller)
Inside popup view controller
var item : Datum! //Datum Codable Root
#IBAction func close_click(_ sender: Any) {
NotificationCenter.default.post(name: Notification.Name(rawValue: "disconnectPaxiSocket"), object: nil)
if let presenter = presentingViewController as? HomeViewController {
presenter.updatitem = item
}
dismiss(animated: true, completion: nil)
}
Once popup dismissed showing tabbar first viewcontroller (index 0), so I added NotificationCenter to call function and change the index.
First of all you don't need to use NotificationCenter for that because you can simply achieve it by passing the UITabBarController object to next view controller when you select any cell in tableView like shown below:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.selectedIndex = indexPath.row
vc.tabBar = self.tabBarController
self.present(vc, animated: true, completion: nil)
}
and you need to declare var tabBar: UITabBarController? into DetailViewController which will hold the value passed by previous view controller.
Then when you dismiss the view controller you can simply select the another index of UITabBarController with self.tabBar?.selectedIndex = 1 and you can simply pass the value to first index view controller like:
#IBAction func close_click(_ sender: Any) {
dismiss(animated: true, completion: {
if let secondTab = self.tabBar?.viewControllers?[1] as? SecondViewController {
secondTab.selectedIndexFromFirstTab = self.selectedIndex
secondTab.tfData = self.userTF.text!
}
self.tabBar?.selectedIndex = 1
})
}
and you need to declare objects in SecondViewController like:
var selectedIndexFromFirstTab = 0
var tfData = ""
Which will hold the data of previous view.
Now your result will look like:
For more info you can refer THIS demo project.
EDIT:
So from your code I have seen that you have embed in navigation controllers to your tabBar controllers so you need to update close_click method like shown below:
#IBAction func close_click(_ sender: Any) {
dismiss(animated: true, completion: {
if let navView = self.tabBar?.viewControllers?[1] as? UINavigationController {
if let secondTab = navView.viewControllers[0] as? HomeViewController {
secondTab.selectedIndexFromFirstTab = self.selectedIndex
secondTab.item = self.item
secondTab.tfData = "Sample"
}
}
self.tabBar?.selectedIndex = 1
})
}
You could use the completion handler of the dismiss method.
dismiss(animated: true, completion: nil)
Instead of passing nil, Pass a method to change the Tab bar to second tab.
The completion handler in the dismiss view controller, gets called when the dismissing of the view controller is completed.
viewController.dismissViewControllerAnimated(true, completion: {
FunctionToChangeToSecondTab()
})
I have a UITableViewController displayed as a popover as follows
popoverViewController = self.storyboard?.instantiateViewController(withIdentifier: "ItemsTableView") as? ItemsTableViewController
popoverViewController?.itemeSelectionDelegate = self
popoverViewController?.modalPresentationStyle = .popover
let popoverPresentationViewController = popoverViewController?.popoverPresentationController
let itemCell = cell as! ItemContentCell
popoverPresentationViewController?.permittedArrowDirections = UIPopoverArrowDirection.any
popoverPresentationViewController?.sourceView = cell
popoverPresentationViewController?.sourceRect = itemCell.itemName.bounds
present(popoverViewController!, animated: true, completion: nil)
popoverViewController is a member var, I am using it to dismiss when from popoverViewController an item is selected, I am calling a custom delegate method from which I am dismissing it using "Dismiss" method but it sometimes works sometimes it doesn't.
func itemSelected(item: Item) {
print("selected item")
popoverViewController?.dismiss(animated: false, completion: nil)
}
Is there any other method to dismiss popover ?
You have to implement UIPopoverPresentationControllerDelegate method public func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool.
Here is demo implementation.
extension YouViewController: UIPopoverPresentationControllerDelegate {
func popoverPresentationControllerShouldDismissPopover(_ popoverPresentationController: UIPopoverPresentationController) -> Bool {
return true
}
}