Swift 3, Successfully passed data but var returns nil when used - ios

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

Related

PersonalityQuiz guided app in swift fundamentals

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
}
}
}

How to perform a segue if child does not exist in Firebase

I would like to check if a user has been banned from a group before to let him have access to the group chat and then perform a segue toward the next view controller.
For that I use exist(). When the child exists, the code works perfectly but when the child does not exist, nothing happens.
I tried to change blockedUserDB.observe(.childAdded, with: { (snapshot) in for blockedUserDB.observeSingleEvent(of: .value without success. I also tried to perform the segue in putting performSegue(withIdentifier: "toConversationControler", sender: Any?.self) in another function triggered when the node does not exist ...
Here is my code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedGroupID = "\(groupArray[indexPath.row].documentID)"
// Check if user blocked
let uid = Auth.auth().currentUser!.uid as String
// 1 - set breakpoint here
let blockedUserDB = self.ref2.child("blocked").child(self.selectedGroupID).child(uid)
blockedUserDB.observe(.childAdded, with: { (snapshot) in
// 2 - does this print?
print("snapshot.exists() ==", snapshot.exists())
if snapshot.exists(){
// 3 - does this print?
print("true rooms exist")
let snapshotValue = snapshot.value as! String
print("\(snapshotValue) the value")
self.blockUserCheck = snapshotValue
print(self.blockUserCheck)
if self.blockUserCheck == "true" {
SPAlert.present(message: "You have been banned from this group")
}
else
{
print("performSegue!")
self.performSegue(withIdentifier: "toConversationControler", sender: Any?.self)
print("selected group \(self.selectedGroupID)")
}
}
else
{
// 4 - does this print?
print("snapshot.exists() was false!")
self.performSegue(withIdentifier: "toConversationControler", sender: Any?.self)
}
})
}
And here is how I prepared for segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let identifier = segue.identifier else {
assertionFailure("Segue had no idientifier")
return
}
if identifier == "toConversationControler" {
let vc = segue.destination as! ConversationViewController
vc.finalGroup = self.selectedGroupID
vc.groupName = self.selectedGroupName
vc.city = self.finalCity
vc.language = self.selectedLanguage
vc.info = self.selectedInfo
}
else if identifier == "toNewConvVC" {
let newConvVC = segue.destination as! NewGroupViewController
newConvVC.city = self.finalCity
}
else {
assertionFailure("Did not recognize storyboard identifier")
}
}
Thank you for your help.

swift - return and pass multiple arrays and an Int via Segue

I'm nearly there with a small basic program I'm writing (still learning) and I've hit a roadblock.
I can now pass 1 array between 2 view controllers and successfully print it when I hit a button in the 2nd one.
However, what I really want to do is pass 2 arrays and an Integer, created from a function on the first VC and have them accessible via the 2nd VC.
Code for 1st VC is here :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
destVC.bothArrays = self.testFunction()
}
}
//Get the new view controller using segue.destinationViewController.
//Pass the selected object to the new view controller.
}
func testFunction() -> [String] {
let randomArray1 = ["blah","blah","ploop"]
let randomArray2 = ["alan", "john"]
let randomInt = 5
return BOTH ARRAYS AND INT TO SEND TO THE NEXT VIEW CONTROLLER?
}
#IBAction func goPressed(_ sender: UIButton) {
performSegue(withIdentifier: "goToNextVC", sender: self)
}
and 2nd VC here :
class WorkoutViewController: UIViewController {
var randomArray1 = [String]()
var randomArray2 = [String]()
var randomInt = 0
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func nowButtonPressed(_ sender: UIButton) {
print(randomArray1)
print(randomArray2)
print(randomInt)
}
}
I can get it working with just one array but I need more than one array and a value to be passed! I've tried playing around with it (e.g. trying '-> [String], [String], Int) but no luck
Any help much appreciated!
You can simply use a tuple to include several variables of different types in a single variable. You should pass the tuple including all 3 variables in your performSegue function as the sender argument, then assign them to the relevant instance properties in the prepare(for:) method.
If you want to keep the function for generating the variables, you should change the return type of the function to a tuple that can fit the 3 variables.
func testFunction() -> (arr1:[String],arr2:[String],randInt:Int) {
let randomArray1 = ["blah","blah","ploop"]
let randomArray2 = ["alan", "john"]
let randomInt = 5
return (randomArray1, randomArray2, randomInt)
}
Then assign the return value of testFunction to the sender input argument of performSegue:
performSegue(withIdentifier: "goToNextVC", sender: testFunction())
Assign the variables:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController, let variablesToBePassed = sender as? (arr1:[String],arr2:[String],randInt:Int) {
destVC.randomArray1 = variablesToBePassed.arr1
destVC.randomArray2 = variablesToBePassed.arr2
destVC.randomInt = variablesToBePassed.randInt
}
}
}
As others have suggested, you can refactor your function to return a tuple, and then use that to pass to your other view controller:
//This is the tuple data type we use to pass 2 string arrays and an Int
typealias parameterTuple = ([String], [String], Int)
func testFunction() -> parameterTuple {
let randomArray1 = ["blah","blah","ploop"]
let randomArray2 = ["alan", "john"]
let randomInt = 5
return (randomArray1, randomArray2, randomInt)
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToWorkout" {
if let destVC = segue.destination as? WorkoutViewController {
//refactor WorkoutViewController to have a parameters property
//of type parameterTuple, split out the tuple and pass each part to
//a different property in your WorkoutViewController
destVC.parameters = testFunction()
}
}
}

collection view cells fetch data

I am new to collection view. I want to retrieve data from CoreData for collection view cell. I know how to retrieve for table view cell but it failed when I use similar way to fetch for collection view. Here are my functions from CoreDataHelper and ViewController class
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let indexPath = collectionView.indexPathsForSelectedItems!
let task = tasks[indexPath.row]
let TaskSettingViewController = segue.destination as! ViewController
TaskSettingViewController.task = task
} else if identifier == "addTask" {
print("+ button tapped")
}
}
}
static func retrieveTasks() -> [Tasks] {
let fetchRequest = NSFetchRequest<Tasks>(entityName: "Tasks")
do {
let results = try managedContext.fetch(fetchRequest)
return results
} catch let error as NSError {
print("Could not fetch \(error)")
}
return []
}
Try this :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as? YourCellName //Cell from which this segue is being performed
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.item] //Downcast to type of task
let objTaskSettingVC = segue.destination as! TaskSettingViewController
objTaskSettingVC.tasks = task
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}
For passing the data from one viewController to other is here
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
if identifier == "displayCellDetail" {
print("Task View cell tapped")
CollectionViewCoreDataHelper.retrieveTasks()
let cell = sender as UICollectionViewCell
let indexPath = self.collectionView!.indexPathForCell(cell)
let task = self.tasks[indexPath.row] as [ToDo]
let objTaskSettingVC = segue.destination as! TaskSettingViewController // ViewController in which you want to send the data
objTaskSettingVC.tasks = [task] //tasks is your variable which is having same type and defined in your TaskSettingViewController
}
else if identifier == "addTask" {
print("+ button tapped")
}
}
}

Swift sending Multiple Objects to View Controller

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)

Resources