I am developing an app in Swift that, in gist, tells people the price of Bitcoin in various currencies. To select the currency, the user chooses from a list in a view controller with a UITableView. This is currencyViewController, and it is presented from my main screen, viewController.
What I want to happen is that, when the user dismisses currencyViewController, it passes a string to a UIButton in the main viewController.
Here's the prepareForSegue function that should pass the data:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "presentCurrency") {
currencySelector.setTitle("\currencySelected", forState: UIControlState.Normal)
}
}
CurrencySelector is a UIButton in the main viewController, and currencySelected is a variable in the second view controller, currencyViewController.
It gives the error "Invalid Escape Sequence In Literal"
So, I've narrowed it down to one of two issues:
The variables from viewController can't be "seen" from currencyViewController. If so, how can I modify the text of CurrencySelector from CurrencyViewController?
For some reason, when the user exits the pushed CurrencyViewControler, prepareForSegue isn't called.
What is going on here? Thanks, and apologies - I am but a swift newbie.
2 - "prepareForSegue" is called when you push a new view controller via the segue, but not when you dismiss it. No segue is called upon dismissal.
1 - A good way to do this would be the delegate pattern.
So the main view controller would be the delegate for the currencyViewController, and would receive a message when that controller is dismissed.
In the start of the currencyViewController file you prepare the delegate:
protocol CurrencyViewControllerDelegate {
func currencyViewControllerDidSelect(value: String)
}
and you add a variable to the currencyViewController:
var delegate : CurrencyViewControllerDelegate?
Now, the mainViewController has to conform to that protocol and answer to that function:
class MainViewController : UIViewController, CurrencyViewControllerDelegate {
//...
func currencyViewControllerDidSelect(value: String) {
//do your stuff here
}
}
And everything is prepared. Last steps, in prepareForSegue (MainViewController), you will set the delegate of the currencyViewController:
var currencyVC = segue.destinationViewController as CurrencyViewController
currencyVC.delegate = self;
And when the user selects the value in currencyViewController, just call that function in the delegate:
self.delegate?.currencyViewControllerDidSelect("stuff")
A bit complex maybe, but it's a very useful pattern :) here is a nice tutorial with more info if you want it:
http://www.raywenderlich.com/75289/swift-tutorial-part-3-tuples-protocols-delegates-table-views
You have to use the parantheses to eval variables in strings, i.e. println("\(currencySelected)")
To access variables in the second view controller (the one which is the destination of the segue) you have to get a reference to it:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "presentCurrency") {
let currencyViewController = segue.destinationViewController as CurrencyViewController // the name or your class here
currencySelector.setTitle("\(currencyViewController.currencySelected)", forState: UIControlState.Normal)
}
}
Related
Sorry, the question is a bit long. Please bear with me.
Basically, I'm trying to write a simple count up/count down ios app using swift. I have three main view controllers. One is an "Initial View Controller" (which is the root view controller) that contain only two buttons - one that presents modally to the actual counting page (second view controller) and another present modally to a tableViewController page (third view controller). so those are the three view controllers.
So, if the user chooses to save the counter they have been counting I want to append the data on that counter view controller to an array I have created to be displayed on the tableViewController. So I'm making the tableViewController a delegate of the Counter View Controller to append the data to the array.
And as to my understanding, you need to implement prepareSegue in tableViewController to connect the tableViewController to the Counter View Controller. However, because the segue to the Counter View Controller doesn't originate from the tableViewController and instead from the Initial View Controller, the prepareSegue function is not working, and thus the delegate doesn't work. So to simplify my question- How would you save(append) data from one View Controller to another View Controller when you segue from a different view controller?
I hope my question was clear. I'm completely new to software development and not sure if I'm making any sense. Thanks so much for the help!
If you have three controllers in the storyboard path, One -> Two -> Three, and you want One to know about data changes in Three, then you need to propagate the changes via Two.
A rough version might look like this:
protocol CountDelegate: class {
func updateCount()
}
class One: UIViewController, CountDelegate {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? Two else { return }
destination.delegate = self
}
func updateCount() {
// whatever
}
}
class Two: UIViewController {
weak var delegate: CountDelegate?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? Three else { return }
destination.delegate = self.delegate // just pass the delegate down
}
}
class Three: UIViewController {
weak var delegate: CountDelegate?
func doWork() {
self.delegate?.updateCount()
}
}
So the delegate is One, and both Two and Three (via Two) point back to it as the delegate.
Solution 1 (Using Notifications):
Each time the counter is updated in CounterViewController, the following code needs to be executed:
let notification = Notification.init(name: NSNotification.Name(rawValue: "UpdateCounter"), object: NSNumber.init(value: 1), userInfo: nil)
NotificationCenter.default.post(notification)
This code is implemented in the Initial View Controller (to observe for the changes in counter):
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(parseNotification(_:)), name: NSNotification.Name.init(rawValue: "UpdateCounter"), object: nil)
}
func parseNotification(_ notification: Notification?) {
if let number = notification?.object as? NSNumber {
/** here counter is a class variable in Initial ViewController **/
counter = number.intValue
}
}
And add the following code in prepareforSegue in the Initial View Controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//Condition to check if TableViewController is being opened
tableViewController.counter = counter
}
Don't forget to remove the observer in the Initial View Controller:
deinit {
NotificationCenter.default.removeObserver(self)
}
Solution 2 (Using Global/Shared variables):
Create a global/shared variable such as
class Shared {
var value = 0
let instance = Shared()
}
In the CounterViewController, you set the value every time the counter is updated
Shared.instance.value = counterValue
You can read the value in TableViewController using the following code:
Shared.instance.value
I have two view controllers, the first one has two buttons, signup and login, the second VC does the function of signup and login stuff (I wrote functions to switch between signup and login mode), is it possible to identify if user pressed login/signup button in the first VC so the right function will be called in the second VC when performing segue?
You have tell the second view controller what to do upon the first view controller selected option (signin or signup). I would assume that you could do this by simply declaring a flag and send it to the second view controller, for instance:
Declare a boolean variable in your second view controller (let's say shouldBehavesAsLogin) which means if selection is login it should be true:
// Controller that could represents signin or signup:
class SecondViewController: UIViewController {
//...
var shouldBehavesAsLogin = false
// ...
}
thus you could determine what is the value that should be assigned to it based on which button tapped, first view controller:
class FirstViewController: UIViewController {
// ...
private var isLoginTapped = false
#IBAction func signinTapped(sender: UIButton) {
isLoginTapped = true
}
#IBAction func signupTapped(sender: UIButton) {
// nothing to do here, isLoginTapped is false by default...
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "MySegue"{
if let nextViewController = segue.destination as? SecondViewController {
nextViewController.shouldBehavesAsLogin = isLoginTapped
}
}
}
// ...
}
Thus all you have to do is to check the value of shouldBehavesAsLogin whether is it true to let the controller behaves as login or false to do the opposite.
Additional Tip:
If the purpose of adding IBActions for each button is just navigating to the second view controller, I would suggest to let both of the buttons to have the same IBAction, but you should let the sender to be of type UIButton instead of Any, thus you could do -for instance-:
#IBAction func aButtonTapped(sender: UIButton) {
// do the default behvior for both signin and signup (navigate to the second controller)
// signinButton is the button you tap for navigating to second controller to behaves as signin
isLoginTapped = sender === signinButton ? true : false
}
This answer assumes there is only one segue between the two view controllers. If there is more than one, you can simply use the segue identifier in prepareForSegue.
I'd handle this by using the sender parameter on either -prepare(for segue:sender:) or -performSegue(withIdentifier:sender), depending on whether you are triggering the segue directly from the button or in code.
If the buttons are triggering the segue, you can test whether the sender params is one of the two buttons concerned, and perform appropriate setup on the destination VC. If the segue is being triggered in code in an IBAction method, you could either pass through the button reference from the IBActions sender parameter, or pass some other identifying object that your prepareForSegue method is able to deal with.
Bear in mind that the target VC's views will not have loaded when prepareForSegue is called, so you may need to set some state on the target VC (e.g. a login/signup enum property) which is then used to set up the VC appropriately when its -viewDidLoad is called.
I am looking for help in figuring out how to have a row in a MultivaluedSection present a view controller with a second Eureka form and return a value back to the MultivaluedSection row. I've been able to get a regular ButtonRow to push a view controller using a segue, but I can't figure out not to get a value back to the row in the MultivaluedSection. I'm not sure if the ButtonRow method supports returning values or not so I started looking for other solutions. One I found is to use a custom presenter row (https://github.com/xmartlabs/Eureka#custom-presenter-rows), but I don't understand how to make that work.
Here one thing I did find, but again, I don't understand how to put this all together:
Help creating simple Custom Presenter Row
- https://github.com/xmartlabs/Eureka/issues/716
Can someone either point me to a working sample or help walk me through getting this setup?
If you are already pushing a new VC with a segue, then you might want to implement a protocol and define the functions to pass data back.
Here is a good tutorial with Navigation controllers where at the end a Protocol is added.
eg:
View one (could be the Form View Controller with the ButtonRow)
class FormVC: FormViewController , FooViewControllerDelegate{
var text : String!
override func viewDidLoad() {
super.viewDidLoad()
}
/// Delegate protocol callback implementation
func myVCDidFinish(controller: FooViewController, text: String) {
// Receive the data as a delegate
self.text = text
// In this case we also want to finish the view
controller.navigationController?.popViewController(animated: true)
}
/// This represents the prepare for segue mentioned as implemented in the question
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Act upon the segue we want from this VC
// The string is defined in the storyboard, so it must be exactly the same
if segue.identifier == "mySegue"{
// Creating the second VC instance
let vc = segue.destination as! FooViewController
// Since this class is now a delegate, setup the delegate
vc.delegate = self
}
}
}
View two (the pushed View controller)
protocol FooViewControllerDelegate {
func myVCDidFinish(controller: FooViewController, text: String)
}
class FooViewController: UIViewController {
/// Data
var text : String!
/// Set up an optional delegate
var delegate:FooViewControllerDelegate? = nil
override func viewDidLoad() {
super.viewDidLoad()
// Init label
self.text = "Pushed view data to pass back
}
}
I have two UICollectionViewControllers and the first one uses a push segue to get to the second one. The problem I'm having is passing information back to the first controller when the back button (the one that gets added automagically) is pressed in the second controller. I've tried using the segueForUnwindingToViewController, and canPerformUnwindSegueAction override functions, but no dice. I need to be able to access both view controllers so I can set some variables. Any ideas?
Here is an example with two view controllers. Let's say that the names of the two view controllers and ViewController and SecondViewController. Let's also say that there is an unwind segue from the SecondViewController to the ViewController. We will pass data from the SecondViewController to the ViewController. First, let's set the identifier of this segue by opening the document outline and selecting the unwind segue. Then open up the attributes inspector and set the identifier to "unwind".
SecondViewController Code:
class SecondViewController: UIViewController
{
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
if let destination = segue.destinationViewController as? ViewController {
if identifier == "unwind" {
destination.string = "We Just Passed Data"
}
}
}
}
}
ViewController Code:
class ViewController: UIViewController {
var string = "The String That Will Be We Just Passed Data"
#IBAction func unwindSegue(segue: UIStoryBoardSegue) {
}
}
It sounds like you are trying to intercept the back button, there are many posts for this on SO, here are two:
Setting action for back button in navigation controller
Trying to handle "back" navigation button action in iOS
In practice, it is more clear to return state in closures (more modern), or delegates.
I am trying to segue between two uiviewcontrollers but Swift will not compile my code. Here is my code:
prepareForSegue(segue: UIStoryboardSegue(identifier: "containerSegue", source: self, destination: EventColumnViewController.self), sender: nil)
Where EventColumnViewController is my custom ViewController class, and here is the error I get:
Cannot find an initializer for type 'UIStoryboardSegue' that accepts an argument list of type '(identifier: String, source: ViewController, destination: EventColumnViewController.Type)'
Why is this happening? How can I fix this?
Here is my class declaration: class EventColumnViewController: UIViewController, UIViewControllerTransitioningDelegate
You don't want to specify a specific segue in the function signature for prepareForSegue. Instead the function receives whichever segue is triggered and you can perform specific code for a certain segue using if blocks.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "containerSegue") {
// perform specific code here.
}
}
If you actually want to perform the specific segue named "containerSegue" programatically, you would want to use a different function:
performSegueWithIdentifier("containerSegue", sender: self)
Notice the difference:
prepareForSegue is used to do setup (such as passing data to the next controller) after a segue has been triggered
performSegueWithIdentifier actually triggers the segue