Cant use Functions in one file in another - ios

I created a file called "Util.swift" and it has a class "class VC: UIViewController" and has a 2 functions:
Next(segue: String) {
performSegueWithIdentifier(segue, completion: nil)
}
Back() {
dismissViewControllerAnimated(true, completion: nil)
}
So basically, the functions just tell the current viewController to go to the next View or go back to the previous view.
However, in another file, I have an IBAction which is attached to a button and in that action I call the function "Back" in the Util file by doing this:
#IBACTION func ~~~ {
nextVC = Util()
nextVC.back()
}
However, when I click the button, it doesn't do anything.
When I try to connect another button to an action that calls the "next" function in the Util file by doing the same thing and putting in the segue indetifier parameter, it says SIGABRT error: no segue identifier called "xxx".
Can anyone help me? Should I simply use XIB and not use segues?

You have to initialize the UIViewController either with storyBoard or with XIB. If you are doing with factory init() method then it doesn't do anything for you; that means, that is not a viewController which is associated either with storyBoard or XIB. So all you need to do is instantiate the viewController with either options. Before that you need to set an identifier for the viewController to instantiate it.
let storyBoard = UIStoryBoard(name: "MainStoryBoard", bundle: nil)
let vc = storyBoard.instanstiateViewControllerWithIdentifier("YOUR_IDENTIFIER") as! Util
//call your Next method like this
vc.Next(segue:"identifier")
Please refer the document
Apple Doc

In order to use segues, the view controller instance needs to be associated with a Storyboard. In order to be associated with a storyboard, a view controller instance either needs to be the result of a segue or instantiated from a storyboard via instantiateViewControllerWithIdentifier.
When you create an instance of Util via Util() you have a view controller instance that isn't associated with a storyboard and isn't actually presented on screen.
As a result, when you try and perform a segue, you get an error since the segue can't be found.
Also, when you try and dismiss the view controller in back() you are trying to dismiss a view controller that isn't presented.
I am not sure why you want to wrap two fairly simple functions inside next and back, but you can do this using a superclass for all of your view controllers and have this superclass implement your next and back functions:
class MyViewContollerSuperclass: UIViewController {
func next(segue: String) {
self.performSegueWithIdentifier(segue, sender: self)
}
func back() {
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Then you actual view controller would be declared as:
class MyActualViewController: MyViewContollerSuperclass {
#IBACTION func ~~~ {
self.back()
}
}

Related

Is it possible to create unwind segues programatically?

My app has a navigation controller where the first view controller is a login screen and all other screens are pushed on top.
I would like to unwind to the login screen whenever any http request to the backend returns a 401 error.
What I had in mind was to add an extension to the ViewController class with something like this:
extension UIViewController {
func unwindToLoginScreen() {
performSegue(withIdentifier: loginScreen)
}
}
And the segue would be an unwind segue. Then, whenever the request fails I call the view controller's unwindToLoginScreen method.
However, the problem with this approach is that I would have to remember to create said unwind segues on the storyboard for all new view controllers that I added to the project.
So far I think the ideal plan would be to be able to create the unwind segue programatically instead of using the storyboard. So, my unwindToLoginScreen() extension method would work in any new view controller by default. Something like:
extension UIViewController {
func unwindToLoginScreen() {
let segue = UnwindSegue(identifier: "blablah", segue: LoginViewController.unwindToLoginViewController)
segue.perform()
}
}
Is it possible to do something like this?
You can't create segues in code, but you can pop to the root of the UINavigationController's stack:
func returnToLoginScreen() {
self.navigationController?.popToRootViewController(animated: true)
}
If you want to pop to a viewController that isn't the root, you can find it in the array of viewController's managed by the UINavigationController and then call popToViewController:
// pop to second viewController in the stack
self.navigationController?.popToViewController(self.navigationController!.viewControllers[1], animated: true)
... or search for the ViewController by type:
if let loginVC = self.navigationController?.viewControllers.first(where: { $0 is LoginViewController }) {
self.navigationController?.popToViewController(loginVC, animated: true)
}

How to pop back to a TableViewController?

I start of with a tableViewController that has a list of names. When the user taps on a name, they are segued to a view controller.
While in that viewController the user may press a button that will take them to another table view Controller.
The layout is like this:
TableViewController(1) -> ViewController -> TableViewController(2)
My question is, how can I pop back to the first TableViewController from the Second TableViewController.
My rootViewController is my signIn View controller so I cannot pop back to root.
You can run this to pop to your rootViewController:
self.navigationController?.popToRootViewController(animated: true)
Update:
Since your rootViewController is not where you want to end up then you can iterate through your controllers and pop to a specific one:
for controller in self.navigationController!.viewControllers {
if controller.isKind(of: TableViewControllerOne.self) {
self.navigationController!.popToViewController(controller, animated: true)
break
}
}
Instead of TableViewControllerOne.self update to your desired controller.
If you're familiar with segues, you can implement an unwind segue. That would give you the added benefit of passing information back to TableViewController(1) if you needed to. To make that work in TableViewController1 you would add some code that looked like:
#IBAction func unwind(fromTableVC2 segue: UIStoryboardSegue) {
if (segue.source is TableVC2) {
if let svc = segue.source as? TableVC2 {
// pass information back
}
}
}
Then in your storyboard you would go to where you have your TableVC2 and drag the yellow VC circle to the exit and choose the function we created above. Name the segue (for this example we'll call it "UnwindToTableVC1"), and then somewhere in TableVC2 add the code:
func setVariableToPassBack () {
// Set up variables you want to pass back
performSegue(withIdentifier: "UnwindToTableVC1", sender: self) }
And that will take you back to your chosen destination with any information you wanted to pass back.
If you don't want to pass anything back, you really just need call the below line in your TableVC2:
performSegue(withIdentifier: "UnwindToTableVC1", sender: self)

performSegueWithIdentifier doesn't work in a different ViewController

I have the following storyboard: Main Storyboard
In it, several custom View Controllers are programmatically embedded in the Scroll View. In one of them, a button is present and should trigger a segue to show the "hey" screen.
I have then wrote the following code:
#IBAction func addNewDateButtonDidTouched(sender :AnyObject) {
let mainStoryboard = UIStoryboard(name: "Main", bundle: nil)
let storyboardInit = mainStoryboard.instantiateViewControllerWithIdentifier("mainview")
storyboardInit.performSegueWithIdentifier("showNewDate", sender: self)
}
This #IBAction seems to reload the inital view controller and perform the segue correclty (Xcode doesn't return any error). But the "hey" screen doesn't show up and its viewDidLoad() doesn't load.
Any hint?
Instead of calling
storyboardInit.performSegueWithIdentifier("showNewDate", sender: self)
try
self.performSegueWithIdentifier("showNewDate", sender: self)
The storyboardInit in your code will instantiate a UIViewController that has an identifier "mainview".
But this UIViewController hasn't been added to the screen, when you're asking the controller to perform a segue.
So, I wouldn't do that. What I would do, to segue to the hey screen is either :
Create a segue from the current controller and just do this in the code : self.performSegueWithIdentifier
Instantiate the hey controller like this : instantiateViewControllerWithIdentifier, and then just do the self.presentViewController
So, to transition to a new controller, you have to start from the current controller.
if i understand your viewcontroller hierarchy correctly...
since the viewcontroller that contains the button to trigger the segue is a childviewcontroller of the viewcontroller that has the segue setup in storyboard i think you have to call presentingViewController?.performSegueWithIdentifier("showNewDate", sender: self).

How can I use my action and programmatically transition to a view controller as defined in the storyboard?

I have a sent Action, as follows:
#IBAction func showSettings(sender: AnyObject) {
let settingsPicker = SettingsViewController()
settingsPicker.setDelegate(self)
let navigationController = UINavigationController (rootViewController: settingsPicker)
self.presentViewController(navigationController, animated: true, completion: nil)
}
The method creates a controller, sets a reference to the delegate, and creates a navigation controller.
All this works, however the widgets defined in the story board do not appear. The SettingsViewController should manage a ui which is defined in a story board. I presume becuase I create it programmatically none of the widgets appear. The SettingsViewController does not programmatically create widgets, the are declaratively defined in the story board.
If I link (in the storyboard) the two controllers with a segue, then the widgets appear, but my action is not being used.
How can I use my action and present the view controller / ui as defined in the storyboard?
When you create a segue between your UIViewControllers, you should define an identifier, eg: "settingsSegue".
In your code you can then perform that segue by calling the segue with the identifier:
#IBAction func showSetting(sender: AnyObject) {
performSegueWithIdentifier("settingsSegue", sender: nil)
}
To set up the SettingsViewController you should implement the following:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if let settingsController = segue.destinationViewController as? SettingsViewController {
settingsController.delegate = self
}
}
Interacting with Storyboard and Segues
If you want to invoke a segue through code, see Laffen's answer.
If you want to create a view controller that's defined in your storyboard and then display it programmatically, use instantiateViewControllerWithIdentifier to create a new instance of your view controller, then display it to the screen as desired (present it modally, push it onto your navigation stack, or whatever you want to do.)

How to access a button from another view controller?

Let's say I have a firstViewController and a secondViewController. The first one contains a firstButton and the second one - a secondButton. Here's what I want to do: when user clicks the secondButton, some firstButton's property changes.
Unfortunately, when I create an instance of a firstViewController in a secondViewController and then trying to access a firstButton, I get an error:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
So, technically, I'm trying to do this as follows:
var ins = firstViewController()
#IBAction func secondButtonisPressed(){
ins.firstButton.alpha = 0
}
What is the proper way to implement that?
Thanks in advance.
Your problem here is that the IBOutlets of your firstViewController are only available (!= nil) after the viewDidLoad() firstViewController's method has being called.
In other words, you have to present the view, before you can make any changes to a UIViewController IBOutlet.
How you can solve this?
Add a variable into FirstViewController that works as a flag for you.
for example: var hideFirstButton = false
in the viewDidLoad or viewWillAppear method of FirstViewController check for hideFirstButton's value and hide or show your firstButton.
Then, before you present your FirstViewController change the value of hideFirstButton to the needed for your application to run fine.
UPDATE:
Other workaround, using Storyboard is (This approach has the inconvenient that the completion handler is called after viewWillAppear() so the button is visible for a second):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let firstViewController = storyboard.instantiateViewControllerWithIdentifier("FirstViewController") as! FirstViewController
self.presentViewController(firstViewController, animated: true, completion: {
//This lines will be called after the view is loaded so the code will run
firstViewController.firstButton.alpha = 0
})
EXAMPLE: an example at GitHub
You could try to do this using delegation, similar to the way Apple does it in their existing frameworks. For an example, look at the way that you use UITableViewDelegate when working with a UITableView object.
If you wanted to use delegation to tell secondViewController that firstButton was pressed using delegation, you could do it as follows:
Step 1:
Create a protocol containing a method for the button press event.
protocol buttonPressDelegate {
func buttonPressed() -> Void
}
Step 2:
In firstViewController, declare that you have an instance of an object of type buttonPressProtocol.
var buttonPressDelegateObj: buttonPressDelegate?
Step 3:
In firstViewController, initialize your buttonPressDelegateObj to contain a reference to your instance of secondViewController. If you want you can create a method to set the reference contained in buttonPressDelegateObj, or do it viewDidLoad in firstViewController, etc.
buttonPressDelegateObj = secondViewControllerObj
Step 4:
In secondViewController, declare that you adopt the buttonPressDelegate protocol.
class secondViewController: UIViewController, buttonPressDelegate {
Step 5:
In secondViewController, implement the protocol method buttonPressed() by adding the function with your desired implementation. Here's an example:
func buttonPressed() {
secondButton.alpha = 0
}
Step 6:
Create an #IBAction on the button in firstViewController, so that when the button is pressed it calls buttonPressDelegateObj.buttonPressed() and you can respond to the event
#IBAction func firstButtonPressed() {
if (buttonPressDelegateObj != nil) {
buttonPressDelegateObj.buttonPressed()
}
else {
print("You forgot to set your reference in buttonPressDelegateObj to contain an instance of secondViewController!")
}
}
Note: This is just one way that you could do this. To tell firstViewController that secondButton was pressed (go the other way), have firstViewController implement the protocol buttonPressDelegate, have secondViewController contain a reference to firstViewController as an instance of type buttonPressDelegate?, and create an #IBAction in secondViewController that fires when secondButton is pressed that calls your the buttonPressDelegate method.
Note: There is a similar pattern employed in the Android world to get a Fragment to communicate to an Activity, that you can read more about here

Resources