I would like to implement the following: (I am using navigation controllers)
View A has several options which determine which path to take. The first displays view B which will then display View C using navigation controllers. The tool bar's first item performs a unwind to View A. Which works. The second item in the tool bar, I would like to unwind not only to A but redirect to View E.
The code in A view controller looks like this:
#IBAction func unwindToHomeController(segue: UIStoryboardSegue) {
self.performSegue(withIdentifier: "toPerson", sender: self)
}
When I click on the second item in the tool bar, View E is displayed but view A is immediately displayed after a brief delay.
How do I stop the display of View A?
Perhaps there is a better way.
You need to wait the animation finish to performSegue to E:
class ViewController: UIViewController {
#IBAction func unwindToA(segue: UIStoryboardSegue) {
}
#IBAction func unwindToE(segue: UIStoryboardSegue) {
CATransaction.begin()
CATransaction.setCompletionBlock {
self.performSegue(withIdentifier: "E", sender: nil)
}
CATransaction.commit()
}
}
UPDATED To avoid the flashing showing A while pushing E
1) Remove the unwindToE function:
extension ViewController {
#IBAction func unwindToA(segue: UIStoryboardSegue) {
}
// #IBAction func unwindToE(segue: UIStoryboardSegue) {
// CATransaction.begin()
// CATransaction.setCompletionBlock {
// self.performSegue(withIdentifier: "E", sender: nil)
// }
// CATransaction.commit()
// }
}
2) Create a custom segue:
class MyUnwindSegue: UIStoryboardSegue {
override func perform() {
guard let nav = source.navigationController else { return }
guard let root = nav.viewControllers.first else { return }
let viewControllers = [root, destination]
nav.setViewControllers(viewControllers, animated: true)
}
}
3) Update the segue to MyUnwindSegue in storyboard (make sure the Module is selected to your project module rather than empty):
Related
I am using MVVM Arch with RxSwift, I have one screen commonly used for more than 8 flows, Here I don't want to use enum and switch casses but I have to call different functions based on flow and route to different screens
Ex: I have Two classes A and B and both will go to C, Here C have one button with two functions Now once we navigate to C and I click button in C then it should call function related to A class and route to some other controller
How can I do this without enums
Mean I have to decide in A or B classes which function should call in C after clicking on button
class ViewControllerA: UIViewController {
override func viewDidLoad() {
}
#IBAction func btn_clicked(_ sender: Any) {
let vc: ViewControllerC =
self.storyboard?.instantiateViewController(withIdentifier:
"ViewControllerC") as! ViewControllerC
vc.isFrom = "A"
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewControllerB: UIViewController {
override func viewDidLoad() {
}
#IBAction func btn_clicked(_ sender: Any) {
let vc: ViewControllerC =
self.storyboard?.instantiateViewController(withIdentifier:
"ViewControllerC") as! ViewControllerC
vc.isFrom = "B"
self.navigationController?.pushViewController(vc, animated: true)
}
}
class ViewControllerC: UIViewController {
var isFrom = ""
override func viewDidLoad() {
super.viewDidLoad()
if isFrom == "A"{
self.callForA()
}else if isFrom == "B"{
self.callForB()
}
}
func callForA(){
//Your Code
}
func callForB(){
//Your Code
}
}
I have set a "Show as popover" segue between a UIView (A) and another UIView (B) (embed in a Navigation Controller) activated on a button's clic.
i am trying to pass datas back from (B) to (A) when i dismiss it (i want to keep the popover animation on both ways).
I have tried many methods i found mostly here, on Stackoverflow, but as of now i never successfully retrieved my data on (A).
I tried Delegates and protocols as well as other simpler methods. The last in date is the following one:
In (A), i just try to print the variable that should be storing the datas in ViewWillAppear :
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var testValue:String = ""
override func viewWillAppear(_ animated: Bool) {
print(testValue) // print is empty
super.viewWillAppear(animated)
}
}
In (B), i dismiss the popover and try to send the datas back on a button clic :
class SearchFilterViewController: UIViewController {
#IBAction func DismissPopoverOnClic(_ sender: Any) {
if let navController = presentingViewController as? UINavigationController {
let presenter = navController.topViewController as! SearchBarsController
presenter.testValue = "Test"
print("success") //never called
}
self.dismiss(animated: true, completion: nil)
}
}
on (B) i'd like to set up some filter that i'd use on (A) to present search results in a tableview. But actually the testValue's value is always blank.
oky so you can do it using unwind segue here is sample project :
sample projecct
process :
Add this method to SearchBarsController below viewWillAppear
#IBAction func unWindFromFilterViewController(_ sender: UIStoryboardSegue) {
}
Than go to Storyboard and go to SearchFilterViewController and then cntrl + Drag from DismissPopoverOnClic to top of the exit button then select unWindFromFilterViewController .
Than this the SearchFilterViewController write this method for passing data
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? ViewController {
destVC.testValue = "Test"
}
}
You will get your desired data back . thanks
When passing back data to a viewController, the most efficient way to implement it using delegate
protocol SearchFilterViewControllerDelegate {
func setTextValue(string : String)
}
class SearchFilterViewController: UIViewController {
var delegate : SearchFilterViewControllerDelegate?
#IBAction func DismissPopoverOnClic(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
delegate?.setTextValue(string : "Test Value")
}
}
class SearchBarsController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {
var filterViewController : SearchFilterViewController?
func popup() {
// your pop up code and init filterViewController
filterViewController.delegate = self **//without this line, the delegate will be nil, no nothing will happen.**
}
}
extension SearchBarsController : SearchFilterViewControllerDelegate {
func setTextValue(string : String) {
print(string)
}
}
I have a requirement where I have to call a first view controller function from second view controller on a button tap.
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// Doing ABC
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc & call First View controller method so that it does ABC.
}
My first question is can we initiate First VC IBAction directly from second VC ? Is it possible ?
I am thinking to do following
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
}
func DoABC {
// Doing ABC
}
}
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc
// Call Firstvc.DoABC ?? How to do this ??
}
How to call the first vc method from the second vc ??
You have a few options here:
Split out the logic, call the same code from each view controller
Use a closure callback
Use the delegate pattern as a method of calling back
Option 1 - Split out the logic:
class FirstViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func firstButtonPressed(_ sender: Any) {
abcPerformer.doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
}
}
class SecondViewController: UIViewController {
let abcPerformer = ABCPerformer()
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
abcPerformer.doABC()
}
}
struct ABCPerformer {
func doABC() {
// do ABC
}
}
Option 2 - Create a callback:
class FirstViewController: UIViewController {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.doABC = doABC
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
var doABC: (() -> Void)?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
doABC?()
}
}
Option 3 - Use a delegate:
protocol ABCProtocol {
func doABC()
}
class FirstViewController: UIViewController, ABCProtocol {
#IBAction func firstButtonPressed(_ sender: Any) {
doABC()
}
#IBAction func showSecondVC_ sender: Any) {
// showingSecondVC
secondVC.delegate = self
}
func doABC() {
// do ABC
}
}
class SecondViewController: UIViewController {
weak var delegate: ABCProtocol?
#IBAction func SecondButtonPressed(_ sender: Any) {
// Dismiss second vc & call First View controller method so that it does ABC.
delegate?.doABC()
}
}
There is probably more options too, but these should give you enough choice to make a decision
Create a protocol, say, SecondViewControllerDelegate.
Add a method signature to that protocol, something like secondViewControllerDidPressButton.
Add a var to secondViewController: var delegate: SecondViewControllerDelegate
Update firstViewController to implement that protocol.
In prepareForSegue of firstViewController, assign firstViewController as the delegate for the secondViewController that is about to be presented.
Update secondViewController to call self.delegate.secondViewControllerDidPressButton when the button is pressed.
You can Use custom delegate for that Like below and add function "presentPage" wherever you want to call.
protocol MainDelegate {
func presentPage(page : Int)
}
To present your second view controller from First, You can use push or present transition.
#IBAction func firstButtonPressed(_ sender: Any) {
// call DoABC
//presenting VC
let secondVC = SecondViewController() //change this to your class name
self.presentViewController(secondVC, animated: true, completion: nil)
//for push :
navigationController?.pushViewController(SecondViewController, animated: true)
}
You can use pop/dismiss VC accordingly in your second view to get back first view.
class secondViewController: UIViewController {
#IBAction func SecondButtonPressed(_ sender: Any)
// Dismiss second vc // Call Firstvc.DoABC ?? How to do this ??
//if you used Present in first step then use dismiss
self.dismissViewControllerAnimated(false, completion: nil)
//if you used push in first step then use pop
self.navigationController?.popViewController(animated: true) }
There are two ViewController in my app, ViewController and ViewController2
In ViewController, a button set Present Modally segue to "ViewController2"
And ViewController override viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("will appear")
}
In ViewController2, a button to go back
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
Now it still can trigger viewWillAppear then I go back to ViewController from ViewController2
If I change ViewController2's presentation from Full Screen to Over Current Context, viewWillAppear will not be triggered
How can I trigger some code when go back?
You can do it without giving up storyboard segues, but you nevertheless had to setup will/did Disappear handler in ViewCOntroller2:
class ViewController: UIViewController {
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? ViewController2 {
(segue.destination as? ViewController2).onViewWillDisappear = {
//Your code
}
}
}
}
class ViewController2: UIViewController {
var onViewWillDisappear: (()->())?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
onViewWillDisappear?()
}
...
}
There are several ways to handle this operation. Here is one, which I used to use.
// ViewController1
class ViewController1: UIViewController {
#IBAction func presentOverCurrentContext(button: Button) {
let vc2 = // instantiate ViewController2
vc2.modalPresentationStyle = .overFullScreen
vc2.presentingVC = self // use this variable 'presentingVC' to connect both view controllers
self.present(vc2, animated: true)
}
}
// ViewController2
class ViewController2: UIViewController {
var presentingVC: UIViewController? // use this variable to connect both view controllers
#IBAction func close(button: Button) {
// handle operation here
presentingVC?.viewWillAppear(true)
self.dismiss(animated: true, completion: {
// or here
// presentingVC?.viewWillAppear(true)
})
}
}
You can also use, your own method to reload view/viewcontroller, but viewWillAppear is common accessible method for all view controllers (as part of super class life cycle) hence you may not need to specify custom type of view controller for presentingVC
While the answers so far provided do work I think it's a good idea to show how to do it using a protocol and delegate as that's a clean implementation which then also allows for further functionality to be added with minimal effort.
So set up a protocol like this:
protocol SecondViewControllerProtocol: class {
func closed(controller: SecondViewController)
}
Setup the second view controller like this:
class SecondViewController {
public weak var delegate: SecondViewControllerProtocol?
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.delegate?.close(controller: self)
}
}
Setup the first view controller like this:
class FirstViewController: SecondViewControllerProtocol {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerID",
let secondViewController = segue.destination as? SecondViewController {
secondViewController.delegate = self
}
}
func closed(controller: SecondViewController) {
// Any code you want to execute when the second view controller is dismissed
}
}
Implementing it like this does what the original request was and allows for extra methods to be put in the protocol so that the FirstViewController can respond to other actions in the SecondViewController.
Note:
You might want to move the delegate method call into the closure of the dismiss handler so that you know the method is not called until the SecondViewController is actually gone (in case you try to present another view which would fail). If that's the case you could do this:
#IBAction func close(_ sender: Any) {
self.dismiss(animated: true) {
self.delegate?.close(controller: self)
}
}
In fact you could have a will and did methods and call them like this:
#IBAction func close(_ sender: Any) {
self.delegate?.willClose(controller: self)
self.dismiss(animated: true) {
self.delegate?.didClose(controller: self)
}
}
Which would allow you to do something immediately while the second controller is animating away and then know when it has actually gone.
Best/Clean way to handle this scenario to use call back handler.
Example Code
typealias CloseActionHandler = ()-> Void
class TestController: UIViewController {
var closeActionHandler: CloseActionHandler?
func close(_ handler:#escaping CloseActionHandler) {
self.closeActionHandler = handler
}
#IBAction func closeButtonTapped(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
self.closeActionHandler?()
}
}
class ViewController: UIViewController {
func loadTestController(viewController: TestController) {
viewController.close {
//will be called when user will tap on close button
}
}
}
I have to view controllers, from first controller's menu item click, a segue is performed by 'Present Modally' Segue which will open the second viewController (acting as a menu) when i press Refresh button on the second VC, it is calling the method from the first VC To refresh its data and load it on tableView. the problem is it is call the method from second to first VC but is returning nil on this line.
self.tableView.dataSource = self
i think it is because the first VC is not active properly as it is in the background ( the second VC's background is transparent so that i can see the first VC at the back ). i'm new to swift and i don't know how to deal with it.
EDIT: This is the refresh method
func refresh() {
// self.dismiss(animated: true, completion: nil)
// present(DayView_Controller, animated: true, completion: nil)
// self.dismiss(animated: true, completion: nil) // it is dismissing the first view controller instead of second
////////
Extensions.ShowAlert(self.pwait, sender: self) // here it causing the exception on viewDidLoad
let defaults = UserDefaults.standard
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MM/d/yyyy"
let finaldate = formatter.string(from:date)
if Reachability.isConnectedToNetwork(){
// all the services are being call and getting reloaded on tableview
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self // exception here
}
2nd EIDT: now i'm dismissing the 2nd VC on the refresh click and call the method from 1st VC on the completion, still getting the same error.
#IBAction func Refresh_CLick(_ sender: Any) {
self.dismiss(animated: true, completion: {
DayView_Controller().refresh()
})
// DayView_Controller().refresh()
}
I believe, you need to do something like this:
1) Create protocol:
protocol RefreshDelegate {
func dataChanged(data: [Items])
}
2) In your second view controller add:
var delegate: RefreshDelegate ?
if let del = delegate {
del.dataChanged(data: items) // your refreshed data
}
3) In your first view controller add extension:
extension FirstViewController: RefreshDelegate {
func dataChanged(data: [Items]) {
// update your tableView data -> then reload
}
}
Something like this. You should understand main idea.
Hope it helps
Use Delegate to refresh data
protocol RefreshDelegate: class {
func refreshData(data: Int)
}
In your first view controller add delegate
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, RefreshDelegate {
In your prepare for segue method set delegate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "go" {
let viewController:SecondViewController = segue.destination as! SecondViewController
viewController.refreshDelegate = self
}
}
// Delegate
func refreshData(data: Int) {
tableData = data
self.tableView.reloadData()
}
In second view controller add variable for delegate
weak var refreshDelegate: RefreshDelegate?
Now call refresh delegate from second view controller
#IBAction func refreshTapped(_ sender: UIButton) {
self.dismiss(animated: true, completion: {
self.refreshDelegate?.refreshData(data: 10)
})
}