So I'm writing this segue method in Swift, but when I unwrap controller, it is always none. Without the as? it just downright fails at runtime. Whats going on?
override func prepareForSegue(segue : UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "showCoursesSegue") {
var controller = segue.destinationViewController as? EditViewController
controller!.test = true
}
}
This works fine for me in prepare for segue. Tried actually setting the type of controller to EditViewController and removing that ?.
var dst: NoteViewController = segue.destinationViewController as NoteViewController
Do it the Swift way!
func prepareForSegue(segue : UIStoryboardSegue!, sender: AnyObject!) {
switch segue.identifier! {
case "showCoursesSegue":
switch segue.destinationViewController {
case let controller as EditViewController:
controller.test = true
default:
println("segue.destinationViewController is \(segue.destinationViewController)")
}
default:
println("segue.identifier is \(segue.identifier)")
}
}
The issue here was that in IB, the ViewController hadn't been set up as a custom class. I'd post a picture but haven't got enough rep.
Related
I'm learning swift from couple months and one thing I keep screwing up is passing data between ViewControllers. When I PO the path of the forward looking variable from within the prepareForSegue method, the value is intact. But when the new ViewController actually appears and I checked its value, it is nil at that point. if anyone could point me in the right direction I'd be very appreciative.
class LoginViewController: UIViewController {
var user_ID:String = ""
//this below is within another method activated by button
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
print ("there was an error signing in")
print (error!.localizedDescription)
return
}
else {
//go to home screen
let userUID = result?.user.uid
print (userUID)
self.user_ID = userUID
self.performSegue(withIdentifier: "MainSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//this is the destination VC
let viewController = segue.destination as! ViewController
viewController.user_ID = self.user_ID
//NOTE: if I break here and PO viewConroller.user_ID the value is intact
let homeViewController = (self.storyboard?.instantiateViewController(withIdentifier: "MainVC"))! as! ViewController
//let mainViewController = ViewController()
//mainViewController.user_ID = userUID
self.present(homeViewController, animated: true, completion: nil)
}
class ViewController: UIViewController {
var persons = [Person]()
let db = Firestore.firestore()
var user_ID:String = ""
//NOTE: WHEN `viewdidload` runs value of user_ID is nil
Phillip in the comments above provided the solution. The present ViewController was the problem. The segue already does that. Thank you!!!
In swift you can use a cool feature of the switch statement in prepare(segue:) to create cases based on the type of the destination view controller:
Example:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let detailViewController as DetailViewController:
detailViewController.title = "DetailViewController"
}
case let otherViewController as OtherViewController:
otherViewController.title = "OtherViewController"
}
}
However, what if the segue is triggered by a split view controller, so the destination is a navigation controller, and what you really want to do is switch on the class of the navigation controller's top view controller?
I want to do something like this:
case let nav as UINavigationController,
let detailViewController = nav.topViewController as? DetailViewController:
//case code goes here
Where I have the same construct that I use in a multiple part if let optional binding.
That doesn't work. Instead, I have to do a rather painful construct like this:
case let nav as UINavigationController
where nav.topViewController is DetailViewController:
guard let detailViewController = nav.topViewController as? DetailViewController
else {
break
}
detailViewController.title = "DetailViewController"
That works, but it seems needlessly verbose, and obscures the intent. Is there a way to use a multi-part optional binding in a case of a switch statment like this in Swift 3?
I worked out a decent solution to this problem.
It involves doing some setup before the switch statement, and then using a tuple in the switch statement. Here's what that looks like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let dest = segue.destination
let navTopVC = (dest as? UINavigationController)?.topViewController
switch (dest, navTopVC) {
case (_, let top as VC1):
top.vc1Text = "Segue message for VC1"
case (_, let top as VC2):
top.vc2Text = "Segue message for VC2"
case (let dest as VC3, nil):
dest.vc3Text = "Segue message for VC3"
default:
break
}
}
You might find this extension useful…
extension UIStoryboardSegue {
var destinationNavTopViewController: UIViewController? {
return (destination as? UINavigationController)?.topViewController ?? destination
}
}
Then you can simply…
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destinationNavTopViewController {
case let detailViewController as? DetailViewController:
// case code goes here
}
Note that the ?? destination makes sure the return value is non-optional, and also allows it to work in places where the destination could also be a non-navigation controller.
I don't think there is a way to do this with switch and case, but you can do something closer to what you are looking for with if and case (Update: as Hamish pointed out, the case isn't even needed for this scenario) or just normal if let:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let nav = segue.destination as? UINavigationController,
let detailViewController = nav.topViewController as? DetailViewController {
detailViewController.title = "DetailViewController"
}
if let otherViewController? = segue.destination as? OtherViewController {
otherViewController.title = "OtherViewController"
}
}
Since your switch statement in this example isn't really going to ever be verified by the compiler as handling all cases (because you need to create a default case), there is no added benefit to using switch instead of just if let
Optional binding doesn't really lend itself to switches like you're trying to do.
I understanding the desire to use switches rather than simple if and if else, but it's a bit different conceptually from what switch is meant to do.
Anyway, here are the two options I use for most situations
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case is DetailViewController:
segue.destination.title = "DetailViewController"
case is OtherViewController:
segue.destination.title = "OtherViewController"
default:
break
}
}
or
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = seuge.destination as? DetailViewController {
controller.title = "DetailViewController"
}
else if let controller = seuge.destination as? OtherViewController {
controllercontroller.title = "OtherViewController"
}
}
In my opinion, you should use segue identifiers for controlling flow in this switch. But to answer your question, this should work for you.
switch (segue.destination as? UINavigationController)?.topViewController ?? segue.destination {
...
}
The thing is, according to grammar, you have only one pattern (in your case it is binding) per item in a case item list. Since you have only one item on the input but want to use two patterns, you either want to normalize input (which is in this case appropriate as you can see above) or extend item list (which is inappropriate in this case but I show an example below).
switch ((segue.destination as? UINavigationController)?.topViewController, segue.destination) {
case (let tvc, let vc) where (tvc ?? vc) is DetailViewController:
// TODO: If you await your DetailViewController in navigation or without.
break
case (let tvc as DetailViewController, _):
// TODO: If you await your DetailViewController in navigation but other vc is not in a navigation vc.
default:
fatalError()
}
I have a managedObject that is being passed from 1 view controller to another the first pass works fine but when I try to pass the next object after the relationship has been set it doesn't send anything and comes back as either nil or if I try to use other methods comes back with a syntax error. The code I am using for the view controllers is as follows
View Controller 1, The first object set:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "popOver":
if let VC = segue.destinationViewController as? ClassDeckNameViewController
{
if let ppc = VC.popoverPresentationController {
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
ppc.permittedArrowDirections = UIPopoverArrowDirection.Any
ppc.delegate = self
}
VC.classSave = (sender as! ClassSelection)
}
default: break
}
}
}
#IBAction func buttonPriest(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("ClassSelection", inManagedObjectContext: classMOC!)
let newObject = ClassSelection(entity: entity!,insertIntoManagedObjectContext: classMOC)
newObject.classname = "Priest"
var error: NSError?
if let err = error {
println(err)
} else {
classMOC?.save(&error)
self.performSegueWithIdentifier("popOver", sender: newObject)
}
}
This passes the object without problem to the second view controller but this is the one that won't pass any further to the final presenting controller offering the user the final selections for their "Deck":
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCardSelection" {
let detailVC: CardSelectionViewController = segue.destinationViewController as! CardSelectionViewController
detailVC.passedDeckObject = (sender as! Deck)
}
}
#IBAction func enterButton(sender: AnyObject) {
let entityDescription = NSEntityDescription.entityForName("Deck",inManagedObjectContext: managedObjectContext!)
let storeDeck = Deck(entity: entityDescription!,insertIntoManagedObjectContext: managedObjectContext)
storeDeck.deckname = usersDeckName.text
storeDeck.classSelected = classSave!
var error: NSError?
managedObjectContext?.save(&error)
if let err = error {
status.text = err.localizedFailureReason
} else {
usersDeckName.text = ""
status.text = "Deck Saved"
self.performSegueWithIdentifier("showCardSelection", sender: storeDeck)
}
}
I made passedDeckObject a variable of type Deck? in the final view controller to set the final relationship methods I know I am doing something wrong but I am unsure what! Any help with this would be amazing!
This looks to be a misconfiguration issue where the segue is being triggered directly in the storyboard rather than calling your code. As such the sender is a button rather than the new entity instance you're expecting.
To fix, disconnect the segue in the storyboard and connect (if it isn't already) the button to your action method in the view controller.
I'm trying to build to-do app using Xcode 6 and Swift. I was able to run the app on Xcode 6 dp2 but after updating to dp7 I'm getting this error:
'bool' is not convertible to 'uint8'.
Here is the function with the error:
override func prepareForSegue(segue: UIStoryboardSegue?, sender: AnyObject?) {
**if ((segue && segue!.identifier == "showdetails") != nil)**{
var selectedIndexPath:NSIndexPath = self.tableView.indexPathForSelectedRow()
var detailViewController:DetailViewController = segue!.destinationViewController as DetailViewController
detailViewController.toDoData = toDoItems.objectAtIndex(selectedIndexPath.row) as NSDictionary
}
}
The correct way to write that is:
if (segue != nil && segue!.identifier == "showdetails") {
but an even better way is using optional binding:
if let segue = segue {
if (segue.identifier == "showdetails") {
Note that there are other errors about incorrect usage of optionals. This is the modified code that compiles in playground:
if let segue = segue {
if (segue.identifier == "showdetails") {
var selectedIndexPath:NSIndexPath? = self.tableView.indexPathForSelectedRow()
if let selectedIndexPath = selectedIndexPath {
var detailViewController:DetailViewController = segue.destinationViewController as DetailViewController
detailViewController.toDoData = toDoItems.objectAtIndex(selectedIndexPath.row) as NSDictionary
}
}
}
indexPathForSelectedRow returns an optional, so you have to account for that.
Update: as pointed out by #MartinR, segue is no longer optional, so you can solve the problem by simply updating the function signature, and its implementation should look like:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showdetails") {
var selectedIndexPath = self.tableView.indexPathForSelectedRow()
if let selectedIndexPath = selectedIndexPath {
var detailViewController = segue.destinationViewController as DetailViewController
detailViewController.toDoData = toDoItems.objectAtIndex(selectedIndexPath.row) as NSDictionary
}
}
}
First of all when I read the documentation what I read is :
func prepareForSegue(_ segue: UIStoryboardSegue, sender sender: AnyObject?)
UIKit seems to be updated in Beta7 to use less optionals. So segue may be not optional.
Then even if your function signature is good, you are comparing a Boolean to nil.
This (segue && segue!.identifier == "showdetails") is a boolean. A boolean is either true or false.
And at the very end, here is the best practice to unwrap a variable :
if let mySafeVariable = myOptionalVariable {
if mySafeVariable.attribute == <Whatever> {
}
}
You should. No, wait, you must read the Swift free iBooks.
I'm trying to write prepareForSegue in SWRevealViewController with Swift.
Here is my code:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if(segue!.identifier == "segueName")
{
var someText = "Text"
var rvc:newViewController = segue!.destinationViewController as newViewController
rvc.topText = someText
}
}
In newViewController I have topText as NSString
Of course I got nil text because I should make SWRevealViewControllerSegue but I don't know how it should look in Swift
I've found the solution.
First of all need to configure SWRevealControllerSegue. In swift it should looks like:
if(segue.isKindOfClass(SWRevealViewControllerSegue))
{
var rvcs: SWRevealViewControllerSegue = segue as SWRevealViewControllerSegue
var rvc:SWRevealViewController = self.revealViewController()
rvcs.performBlock = {(rvc_segue, svc, dvc) in
var nc:UINavigationController = dvc as UINavigationController
rvc.pushFrontViewController(nc, animated: true)
}
}
Second. XCode beta works bad right now with IBOutlet and segue like this. With variable everything is ok.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.isKindOfClass(SWRevealViewControllerSegue))
{
var rvcs: SWRevealViewControllerSegue = segue as SWRevealViewControllerSegue
var rvc:SWRevealViewController = self.revealViewController()
rvcs.performBlock = {(rvc_segue, svc, dvc) in
var nc:UINavigationController = self.revealViewController().frontViewController as UINavigationController
nc.setViewControllers([dvc], animated: true)
self.revealViewController().setFrontViewPosition(FrontViewPositionLeft, animated: true)
}
}
}
This doesn't work with the latest version. The release notes state:
Took a cleaner approach to storyboard support. SWRevealViewControllerSegue is now deprecated and you should use SWRevealViewControllerSegueSetController and SWRevealViewControllerSeguePushController instead.
There aren't any swift examples anywhere I can find that explain this though.
The performBlock method no longer exists.
Appreciate any help. It seems impossible to do swift without first learning objective c at the moment :)
What was newViewController? This should work:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if(segue.identifier == "segueName")
{
let someText = "Text"
let rvc = segue.destinationViewController as SWRevealViewController
rvc.topText = someText
}
}