I've got problem with some additional challenges. I need to filter an array of type Question by some property and then pass it into next View Controller via segue. I've done this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .animals
}
} else if sender == quiz4Button {
let vc = segue.destination as? QuestionViewController
vc?.correctQuestions = questions.filter { question in
return question.quiz == .cars
}
}
}
#IBAction func quiz3ButtonTapped(_ sender: UIButton) {
performSegue(withIdentifier: "animals", sender: sender)
}
#IBAction func quiz4Button(_ sender: UIButton) {
performSegue(withIdentifier: "cars", sender: sender)
}
Filtration works but it doesn't pass value to next View Controller. I declared variable in QuestionViewControler like that
var correctQuestions: [Question] = []
But when I need to access it I get error "Index out of range". So I figured that its empty..
Segues been made from buttons to VC
Ok. I've got it. The NavigationController was the problem here. Added into function push through NC and it worked ;) so closed I think
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let sender = sender as? UIButton else {return}
if sender == quiz3Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .animals
}
} else if sender == quiz4Button {
let destinationViewController = segue.destination as? UINavigationController
let questionViewController = destinationViewController?.viewControllers.first as! QuestionViewController
questionViewController.correctQuestions = questions.filter { questions in
return questions.quiz == .cars
}
}
}
Related
I am trying to send multiple objects from my initial view controller to my Username VC. Here is the segue code from my controllers: The issue comes when I add in the code to send the second object, termreport. If I delete the termsM and the assignment, it send the students as usually, but I also need to send the termReport object. How would I fix this?
ViewControler:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let students = sender as AnyObject as? [Student]
else { return }
guard let termsM = sender as AnyObject as? [TermReport] //How would I send both objects?
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = students
secondVC.userWebView = webView
secondVC.terms = termsM // not sending
}
let gradeResponse = try Parser(innerHTML)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.students)
self.performSegue(withIdentifier: "ShowStudents", sender: gradeResponse.termReports) //how would I send both variables?
UsernameVC:
var terms: [TermReport]!
override func viewDidLoad() {
print("TERM \(terms[0].grades[3])")//returns found nil optional ERROR
}
You have to include all of the variables you want to send to another ViewController using a segue into a single object (which can be a collection as well). You either create a custom class/struct that has properties with type [Student] and [TermReport] or put these into a native collection (Tuple or Dictionary).
Create custom struct:
struct TermData {
var students = [Student]()
var termReports = [TermReport]()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueData = sender as? TermData
else { return }
if let secondVC = segue.destination as? UsernameVC {
secondVC.students = segueData.students
secondVC.userWebView = webView
secondVC.terms = segueData.termReports
}
}
let gradeResponse = try Parser(innerHTML)
let termData = TermData(students: gradeResponse.students, termReports: gradeResponse.termReports)
self.performSegue(withIdentifier: "ShowStudents", sender: termData)
So here is my code from VC1 and passing the data to VC2.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedArtist = artists[indexPath.item]
performSegue(withIdentifier: "artistToArtSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController()
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
These codes here in VC2 will print the data
class ArtCollectionController: UICollectionViewController {
var artist = Artist() {
didSet{
print(artist.artistId ?? "did not work")
print(artist.name ?? "what name?")
}
}
var selectedArtist = Artist()
but when I use the the variable in these following test codes in VC2. They return a nil.
func fetchArtForArtist() {
guard let artistId = selectedArtist.artistId else {return}
print(artistId)
let fanRef = FIRDatabase.database().reference().child("art_ref").child(artistId)
fanRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
let artId = snapshot.key
print(artId)
// let dataRef = FIRDatabase.database().reference().child(artId)
// dataRef.observe(.value, with: { (snapshot) in
// let dictionary = snapshot.value as? [String: AnyObject]
// //let art =
// }, withCancel: nil)
}, withCancel: nil)
}
#IBAction func testButton(_ sender: UIBarButtonItem) {
print(selectedArtist.name ?? "no name")
print(12345)
}
override func viewDidAppear(_ animated: Bool) {
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
override func viewDidLoad() {
super.viewDidLoad()
fetchArtForArtist()
selectedArtist = artist
print(artist.name ?? "non")
print(selectedArtist.artistId ?? "no id")
}
Im doing this in storyBoard. Im even using 2 vars seeing if there is a difference. I dont understand why the data is successfully passed to VC2 to a couple variables but when the variable is used it returns a nil. Please help.
The other responses are good, but I prefer a slightly different approach:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let artCollectionController as ArtCollectionController:
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
case let otherViewController as OtherViewController:
//Code for some other case
}
}
By using a switch statement, you have a prepareForSegue that will handle multiple different segues cleanly.
The case let construct is a cool trick that only executes that case if the variable in the switch can be case to the desired type. If it can be cast, it creates a local variable of the desired type.
I prefer deciding what code to execute based on the class of the destination view controller because it's less fragile than using the segue identifier. If you forget to set the segue identifier, or add a second segue later to the same type of view controller, or have a typo in the name of the identifier, that code won't work. If you have a typo in your class name, though, the compiler throws an error.
Because you set the artist property on a new instance of ArtCollectionController, which is destroyed upon the exit of the prepareForSegue function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = ArtCollectionController() // created
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
// destroyed here
}
}
Try this instead:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue",
let artCollectionController = segue.destination as? ArtCollectionController
{
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "artistToArtSegue" {
let artCollectionController = segue.destination as! ArtCollectionController
artCollectionController.artist = selectedArtist
artCollectionController.selectedArtist = selectedArtist
}
}
try this, you are creating one more ArtCollectionController instead of passing data to segue one
I have this function inside class firstViewController that produces itemID
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
// Check if the metadataObjects array is not nil and it contains at least one object.
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR/barcode is detected"
return
}
//Get metadata object
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if supportedCodeTypes.contains(metadataObj.type) {
//if the found metadata is equal to the QR code metadata then update the status label's text and set the the bounds
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
let itemID = metadataObj.stringValue
print(itemID)
}
}
}
I want to call itemID in another viewController called secondViewController. How do I call itemID from the firstViewController inside the secondViewController?
You just need to pass that on "prepareForSegue".
just make sure you declare itemId as variable on secondviewcontroller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "secondViewControllerSeque") {
let secondViewController = segue.destination as! SecondViewController
secondViewController.itemID = self.itemID
}
}
If your segueing from the FirstViewController to SecondViewController you can pass the data like this.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "SecondViewControllerSegue" {
let secondViewController = segue.destination as? SecondViewController
if let itemID = itemID {
secondViewController.itemID = itemID
}
}
}
But don't forget to create itemID variable in FirstViewController and instead write the code like this
var itemID: String?
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
itemID = metadataObj.stringValue
}
The firstViewController class needs a member variable, itemID. Then the second viewcontroller should be able to reference it.
Right now itemID is a local variable in your captureOutpur function.
This goes away once the function is finished running. Assign it to the member instead.
If you are using a segue, you only need to do this:
FirstViewController (note: use capital letter on name):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowNextView" { // the identifier is what you named it
if let vc = segue.destination as? SecondViewController {
vc.itemID = itemID // itemID is accessible here, probably global
}
}
}
SecondViewController:
var itemID:String
func viewWillAppear() {
// code to use itemID value
}
Just need to follow 3 steps, let's assume you want to pass data from ViewControllerA to ViewControllerB:
1.create a segue between ViewControllerA and ViewControllerB
2.name the segue with a Identifier in the attributes inspector of it
3.override the prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) at ViewControllerA
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "MySegueID" {
if let destination = segue.destinationViewController as? ViewControllerB {
destination.myInformation = self.myInformation
}
}
}
At first declare a variable in FirstViewController.
var itemID: String?
And call your function to assign your value to that variable before navigation to SecondViewController.
if metadataObj.stringValue != nil {
self.itemID = metadataObj.stringValue
}
Now, declare itemIdForSVC public variable in SecondViewController also,
public var itemIdForSVC: String?
If you are using Storyboard then pass parameter like this while navigating,
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "secondViewControllerSeque") {
let secondViewController = segue.destination as! SecondViewController
secondViewController.itemIdForSVC = self.itemID
}
}
Or if you are doing all programmatically then pass like this while navigating,
let secondViewConttoller = SecondViewController()
secondViewController.itemIdForSVC = self.itemID
self.navigationController?.pushViewController(secondViewConttoller, animated: true)
I have marked the part of my code where the problem is, it is commented out. The error message is:
Cannot assign value of type String! to type UILabel!.
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageLabelDos = sendingText
}
}
}
The label it should be changing is in my DetailViewController and it is a label. The code above is from my original ViewController. How can I make this work?
More code to put in context:
if metadataObj.stringValue != nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("SendDataSegue", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.viaSegue = sendingText
}
}
}
You need to pass the String instead of setting text to label, because when you correct it and set like this sendToDetailViewController.messageLabelDos.text = sendingText, you will get nil error because messageLabelDos is not initialize yet, so try like this. Create one string instance in DetailViewController and use that inside prepareForSegue for passing String and then use that String instance in viewDidLoad to assign Label to text.
class ViewController: UIViewController {
//Your other methods
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SendDataSegue" {
if let sendToDetailViewController = segue.destinationViewController as? DetailViewController {
var sendingText = metadataObj.stringValue
sendToDetailViewController.messageDos = sendingText
}
}
}
}
Inside DetailViewController
var messageDos: String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.messageLabelDos.text = messageDos
}
I have this prepareForSegue in my first ViewController with a enum in it
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
enum TypeOfSegue {
case Edit, Create
}
if let DestViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "EditItem" {
let edit = TypeOfSegue.Edit
}
if let DestViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "CreateNewItem" {
let create = TypeOfSegue.Create
}
}
I want to pass the constants edit or create to my DetailViewController. If possible, how can I do this and under what variable will the TypeOfSegue be saved in DetailViewController?
You can define a mode property in your DetailViewController
class DetailViewController:UIViewController {
var mode: TypeOfSegue!
}
Next you can populate it
enum TypeOfSegue {
case Edit, Create
}
class ListViewController:UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "EditItem" {
destViewController.mode = .Edit
}
if let destViewController: DetailViewController = segue.destinationViewController as? DetailViewController where segue.identifier == "CreateNewItem" {
destViewController.mode = .Create
}
}
}
Update
You can also implement the prepareForSegue method this way
enum TypeOfSegue: String, CustomStringConvertible {
case Edit = "EditItem", Create = "CreateNewItem"
var description: String { return self.rawValue }
}
class ListViewController:UIViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
guard let
destViewController = segue.destinationViewController as? DetailViewController,
identifier = segue.identifier,
segueType = TypeOfSegue(rawValue: identifier) else { fatalError("Wrong segue") }
destViewController.mode = segueType
}
}