Pass NSString from an UIViewController to another - ios

I am trying to pass a string value from a ViewController to another:
#IBAction func unwindToMasterViewController(segue: UIStoryboardSegue) {
let viewController:MyViewController = segue.sourceViewController as! MyViewController
sprite = viewController.sp //sp is NSString declared in MyViewController
print("the string is \(viewController.sp)")// nothing happens!!!
}
I can see that sp IS NOT nil in my log before "leaving" MyViewController.
Is there a better way to do this?
EDIT:
I think I may know the issue:
In log the #IBAction func unwindToMasterViewController(segue: UIStoryboardSegue) is called before I get the string value from MyViewController.
The order the log appears is:
"the string is "
and then the print from MyViewController with the actual value:
"sp is testString"
How do I solve this?

I did this and it worked for me
#IBAction func cancelButtonTapped(sender: UIButton) {
self.dismissViewControllerAnimated(true, completion: nil)
let VC = ViewController()
let string = VC.sp
println(string)
delegate?.addTaskCanceled!("task canceled")
}
and in the main VC I had this at the top:
let sp:NSString = "Test"

Ok. Here's the solution in case anyone else come across this.
The problem was (as suspected) that the segue was called BEFORE I could pass the value to the NSString. That was because I was using the unwind segue connected straight from IB to the MasterView.
I had to delete the unwind segue and do it programmatically when my UITbleViewCell was tapped (didSelectRowAtIndexPath).
Then the right sequence of events unfold:
Get the value from cell and then execute the segue.
That way the value was passed to the MasterViewController as expected.
It goes beyond me why this happened or what is the reason to call the unwind segue before the action is called, but Apple works in mysterious ways...

Related

Could not cast value of type 'UITabBarController' to 'ViewController'

The situation: when I press a button in rentViewController, it pops up a tableviewcontroller. If a specific cell has been pressed, it sends data to rentViewController. In order to send data from one view controller to another I needed the code
let rentViewController : RentViewController = self.presentingViewController as! RentViewController <- here is where the error shows up
so that tableviewcontroller could get access to the variables and functions from rentviewcontroller. I'm using
self.dismiss(animated: true, completion: nil)
to get out of the tableviewcontroller and back to the rentviewcontroller. However, it gives me an error "Could not cast value of type 'UITabBarController' to 'RentViewController'". I did some research and I think it's according to the orders of my view controllers but I'm not sure how to change it in a way that it works. My initial view is 'TabBarController' and the order after that is 'NavigationController' -> 'RentViewController' -> 'TableViewController'. If you have questions feel free to ask I can provide you more information.
Your viewController is being presented from UITabBarController. With approach you are using I believe you can access it like this (where index is index in UITabBarController of your UINavigationController containing RentVC):
if let tab = self.presentingViewController as? UITabBarController,
let nav = tab.viewControllers?[index] as? UINavigationController,
let rentViewController = nav.viewControllers.first as? RentViewController {
rentViewController.data = data
}
However, I would suggest using a delegate or callback block to pass data in this occassion.
For delegate approach, first create protocol:
protocol PassDataDelegate:class {
func passData(data:YourType)
}
Then in TableViewController:
class TableViewController: UIViewController {
weak var delegate: PassDataDelegate?
}
And in RentViewController:
extension RentViewController: PassDataDelegate {
func passData(data:YourType) {
//use data to suit your needs
}
}
Before presenting TableViewController, in RentViewController, set its delegate:
tableViewController.delegate
present(tableViewController, animated: true)
And finally, inside TableViewController, before dismissing, call delegate's method to pass data:
delegate?.passData(data: <<someData>>)

viewDidLoad() is running before Alamofire request in prepareforsegue has a chance to finish

I have two viewControllers that are giving me trouble. One is called notificationAccessViewController.swift and the other is called classChooseViewController.swift. I have a button in the notificationAccessViewController that triggers a segue to the classChooseViewController. What I need to do is within the prepareForSegue function, perform an Alamofire request and then pass the response to the classChooseViewController. That works! BUT, not fast enough.
Below is my prepareForSegue function within the notificationAccessViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let DestViewController = segue.destination as! classChooseViewController
Alamofire.request("MYURL.com").responseJSON { (response) in
if let JSON : NSDictionary = response.result.value as! NSDictionary?{
let classNameArray = JSON["className"] as! NSArray
print("---")
print(classNameArray)
DestViewController.classNameArray = classNameArray
}
}
}
I have it printing out classNameArray to the console, which it does SUCCESSFULLY. Although, in my classChooseViewController.swift, I also print out the classNameArray to the console and it prints with nothing inside of it. This is because the viewDidLoad() function of the classChooseViewController.swift is running before the prepareForSegue function has finished. I want to know what I can do to ensure that the classChooseViewController does not load until the prepareForSegue function has FINISHED running.
Below is my code for classChooseViewController
class classChooseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var classNameArray: NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
print(classNameArray)
}
}
I have attached a picture of what the console reads. You can see that the classNameArray prints from the classChooseViewControllers viewDidLoad() function BEFORE the prepareForSegue() function because the "---" prints out after.
Your design is wrong.
The Alamofire request function is asynchronous. It returns immediately, before the results are ready. Then it invokes the completion handler that you pass in.
I tend to agree with Paul that you should make the request in the destination view controller, not in the current view controller.
If you DO want to fetch the data in Alamofire before you segue to the other view controller then you'll need to write a method that starts the request, then invokes the segue to the other view controller from the Alamofire.request() call's completion handler.
If you're invoking the new view controller from a button press, you need to not link the button directly to a segue, but instead write an IBAction method that triggers the AlamoFire request call, then invokes the segue (or instantiates the view controller directly and pushes/presents it manually) from the completion handler.
Something like this:
#IBAction buttonAction(sender: UIButton) {
Alamofire.request("MYURL.com").responseJSON {
(response) in
if let JSON : NSDictionary = response.result.value as! NSDictionary? {
let classNameArray = JSON["className"] as! NSArray
print("---")
print(classNameArray)
let theClassChooseViewController = storyboard.instantiateViewController(withIdentifier:"ClassChooseViewController" as ClassChooseViewController
theClassChooseViewController.classNameArray = classNameArray
presentViewController(theClassChooseViewController,
animated: true,
completion: nil)
}
}
}
By the way, class names and types should start with an upper case letter, and variable names should start with a lower-case letter. This is a very strong convention in both Swift and Objective-C, and you'll confuse the hell out of other iOS/Mac developers unless you follow the convention.

How to pass information back with view controllers

I have a view controller and one table View Controller. I go from VC One to VCTable. On VCTable, I select a(cell) data from type string that I store in value. When I press a cell, I would like to send that data back to VC One.
and show in button or label.
How to do this using Storyboards?
you should take a look at protocols / delegation:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
First Solution: Using CallBack
In your VCOne:
#IBAction func goToViewController2(sender: AnyObject) {
let vc2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
vc2.callback = ({ string in
self.myString = string
})
presentViewController(vc2, animated: true, completion: nil)
}
in your VCTable:
create a callback variable:
var callback: ((String) -> Void)?
in your didSelectRowAtIndexPath method, send it to VCOne by:
callback?(textField.text!)
Second Solution: Using Reference
#IBAction func goToViewController2(sender: AnyObject) {
let vc2 = storyboard?.instantiateViewControllerWithIdentifier("ViewController2") as! ViewController2
vc2.vc1 = self
presentViewController(vc2, animated: true, completion: nil)
}
in your VCTable:
create this variable:
var vc1: ViewController?
in your didSelectRowAtIndexPath method, send it to VCOne by:
vc1?.myString = textField.text!
Third Solution: Using Delegate see the link as #Andre Slotta said.
FourthSolution: Using CenterNotification Googling for this :).
Hope this help :)

NSUserDefaults key becomes nil when sent between view controllers

I need to send a username between two view controllers so that the second view controller knows who to send a message to. I have tried prepareForSegue, however I have found that the variable passed cannot be dynamically altered. I decided to use NSUserDefaults, which worked very well for the length of my development process. Today, it stopped working. I do not think I deleted anything or made an changes, but nevertheless NSUserDefaults is no longer reliably carrying the value between the two view controllers. Every once in a while (maybe 20% of the time?) the value will be correctly passed. The rest of the time, nothing comes through.Code:
Set key:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
NSUserDefaults.standardUserDefaults().setValue("thisisatest", forKey: "testKey")
NSUserDefaults.standardUserDefaults().synchronize()
self.performSegueWithIdentifier("toChat", sender: self)
}
Note: In the viewDidLoad I set testKey = ""
Retrieve key on new view controller:
override func viewDidLoad() {
super.viewDidLoad()
let theKey = NSUserDefaults.standardUserDefaults().valueForKey("testKey")
print("The Key: \(theKey)")
refreshTable()
let swipe: UISwipeGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "dismissKeyboard")
swipe.direction = UISwipeGestureRecognizerDirection.Down
self.view.addGestureRecognizer(swipe)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
testLabel.text = ""
self.messages.addObject("Loading......")
}
Every time, the default comes up empty: The Key: Optional()I can successfully print the default after setting it, however it disappears once I am segued to the next view controller...If anyone else has experienced this problem please let me know.Thanks
Randy's code:
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via t's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ChatVC") as? chatViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
Added this to destinationviewcontroller:
var friendChosen: String!
The methods for NSUserDefaults are setObject:forKey: and objectForKey:, not setValue:forKey: (Or look at the special methods for specific object types, like setBool:forKey: or stringForKey: (I don't think there's a custom set method for strings.))
The methods with "value" in their names are KVC methods.
But, as Randy says, using your app's model is a better way to go, or passing the information directly to a property in the destination view controller in prepareForSegue. Using NSUserDefaults would not be my first, or my second, choice in this situation.
It looks like you're using storyboards already so it should be pretty easy to pass information using prepareForSegue like this.
class DestinationVC : UIViewController {
var destName : String!
override func viewWillAppear(animated: Bool) {
//configure UI with the destName
self.label.text = destName
}
}
class PresentingVC : UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destinationVC = segue.destinationViewController as? DestinationVC {
destinationVC.destName = "Some String to Pass"
}
}
}
As already mentioned NSUserDefaults is not ideal. You will also be loosing type safety and relying on string matching with keys in NSUserDefaults rather than autocompleting and compiler checking with a var on the destinationVC. It's also good practice to limit where your data is kept and where it could be altered. Storing something in NSUserDefaults when the use case is quite confined will make it more difficult to write focussed tests and make it vulnerable to change from any class anywhere in the app. It may be an edge case but starting a pattern like this in your app could expose you to all sorts of side effect bugs in the future.
Ultimately, this type of information should be passed from view controller to view controller in a model via a delegate. That would be the "appropriate" way to achieve this behavior via a true MVC pattern.
Having said that; I think the quickest fix for you would be not to use segues and to avoid NSUserDefaults all together.
Try the following...
func chooseFriend(sender: UIButton) {
let requestIndex = sender.tag
let friendChosen = self.friends.objectAtIndex(requestIndex) as! String
// Instantiate the second view controller via it's identifier in the storyboard
if let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("SecondViewControllerIdentifier") as? SecondViewController {
// Set the chosen friend
secondViewController.friendChosen = friendChosen
self.presentViewController(secondViewController, animated: true, completion: nil)
}
}
And in the SecondViewController add the following property.
var friendChosen: String!
Please make sure the value is not nil prior to passing it to the destination view controller

Passing data to another ViewController in Swift

Before I begin, let me say that I have taken a look at a popular post on the matter: Passing Data between View Controllers
My project is on github https://github.com/model3volution/TipMe
I am inside of a UINavigationController, thus using a pushsegue.
I have verified that my IBAction methods are properly linked up and that segue.identifier corresponds to the segue's identifier in the storyboard.
If I take out the prepareForSegue: method then the segue occurs, but obviously without any data updating.
My specific error message is: Could not cast value of type 'TipMe.FacesViewController' (0x10de38) to 'UINavigationController' (0x1892e1c).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
if segue.identifier == "toFacesVC" {
let navController:UINavigationController = segue.destinationViewController as! UINavigationController
let facesVC = navController.topViewController as! FacesViewController
facesVC.balanceLabel.text = "Balance before tip: $\(balanceDouble)"
}
}
Below is a screenshot with the code and error.
side notes: using Xcode 6.3, Swift 1.2
A couple of things:
1: change your prepareForSegue to
if segue.identifier == "toFacesVC" {
let facesVC = segue.destinationViewController as! FacesViewController
facesVC.text = "Balance before tip: $\(balanceDouble)"
}
2: add a string variable to your FacesViewController
var text:String!
3: change the FacesViewController viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
balanceLabel.text = text
}
The reasons for all the changes: the segue destinationViewController is the actual FacesViewController you transition to -> no need for the navigationController shenanigans. That alone will remove the "case error", but another will occur due to unwrapping a nil value because you try to access the balanceLabel which will not have been set yet. Therefore you need to create a string variable to hold the string you actually want to assign and then assign that text in the viewDidLoad - at the point where the UILabel is actually assigned.
Proof that it works:
4: If you want display two decimal places for the balance you might change the String creation to something like (following https://stackoverflow.com/a/24102844/2442804):
facesVC.text = String(format: "Balance before tip: $%.2f", balanceDouble)
resulting in:

Resources