Transfer data using prepare for segue - ios

I'm new to swift and firebase services,
I'm using fire store data base as my database and I have a first table view that reads all the data and put it in a nice tableview. every document in my table view has a sub collection. when a user press a row I want it to open a second table view with the sub collection.
this is my prepare for segue code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableViewDishes.indexPathForSelectedRow {
db.collection("Restaurants").document("Applebees").collection("Menu").document(sections[indexPath.section].sectionName!).collection("Dishes").document(sections[indexPath.section].listofDishes![indexPath.row].DishName).collection("Options").getDocuments { (querySnapshot, error) in
if error != nil {print(error)}
else {
for document in querySnapshot!.documents {
//adding all the data to an array called myOption
}
}
}
let selectedDishTableViewController = segue.destination as! SelectedDishViewController
selectedDishTableViewController.myOption = self.myOption
selectedDishTableViewController.dish = self.sections[indexPath.section].listofDishes?[indexPath.row]
selectedDishTableViewController.sectionName = sections[indexPath.section].sectionName!
self.myOption.removeAll()
}
}
the issue is that once my code reach the db.collection line it jumps right away to after the for loop when myOption is a empty array and only then it comes back and appending objects to my array.
that cause the first time I press a row get an empty second table view and when I go back and press it again I get the required information.

db.collection() does some async work, so all this code:
let selectedDishTableViewController = segue.destination as! SelectedDishViewController
selectedDishTableViewController.myOption = self.myOption
selectedDishTableViewController.dish = self.sections[indexPath.section].listofDishes?[indexPath.row]
selectedDishTableViewController.sectionName = sections[indexPath.section].sectionName!
self.myOption.removeAll()
should be right before the for loop where you set myOption array. That way, everything will be set up once the database has retrieved all the data and you have made all the setup inside the for loop.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableViewDishes.indexPathForSelectedRow {
db.collection("Restaurants").document("Applebees").collection("Menu").document(sections[indexPath.section].sectionName!).collection("Dishes").document(sections[indexPath.section].listofDishes![indexPath.row].DishName).collection("Options").getDocuments { (querySnapshot, error) in
if error != nil {print(error)}
else {
for document in querySnapshot!.documents {
//adding all the data to an array called myOption
}
let selectedDishTableViewController = segue.destination as! SelectedDishViewController
selectedDishTableViewController.myOption = self.myOption
selectedDishTableViewController.dish = self.sections[indexPath.section].listofDishes?[indexPath.row]
selectedDishTableViewController.sectionName = sections[indexPath.section].sectionName!
self.myOption.removeAll()
}
}
}
}
Since its an Async call you might have to write the selectedDishTableViewController segue in the callback.
Recommendation : Seque delegate method should be simple and should not be handling all these db operation try to optimise it.

Related

How can I transfer multiple rows of a tableView to another ViewController

I'm trying to add a feature in my app, to add multiple members to one action at one time. The members are listed in a tableView and the user can select multiple rows at one time with the .allowsMultipleSelection = true function. I got the following code but this doesn't work. I think my idea would work but not in the way I have it in the code :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedRows = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
destination.members = members[selectedRows]
}
Does somebody out here know, how I can solve this problem, because there is an error :
Cannot subscript a value of type '[Member?]' with an index of type '[IndexPath]'
I have the same feature in the app but just for one member. There I in the let selectedRows line after the indexPathForSelectedRow a .row. Is there a similar function for indexPathsForSelectedRows ?
Or is this the wrong way to do it?
You need
destination.members = selectedRows.map{ members[$0.row] }
As the indexPathsForSelectedRows indicates, it returns an array of IndexPath. What you need to do is create an array of Member objects based on those path.
Assuming you have a "members" array that contain all the members the user can select from, and your table has only 1 section:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var selectedMembers: [Member] = []
guard let destination = segue.destination as? AddMultipleMemberTransactionViewController,
let selectedIndexes = multipleMemberTableView.indexPathsForSelectedRows else {
return
}
for selectedIndex in selectedIndexes {
let selectedMember = members[selectedIndex.row]
selectedMembers.append(selectedMember)
}
destination.members = selectedMembers
}
You can also use the array map() function to change the for loop into a single line operation:
let selectedMembers: [Member] = selectedRows.map{ members[$0.row] }
Both should effectively do the same.

transfer weather data to container view

Im unable to update labels in container view. Here's how i've done it.
​
I wrote my updateWeather function in main VC and retrieved the weather data successfully. when i printed weatherJSON it shows all the received data in console.
now when i started writing updateUI function i could only update the labels on main VC.
so i used prepare segue to send data to container view and sent a string to container VC and updated "humidity" label successfully. all labels accept strings without any issues.
but i have no idea how to send weather data to container view.
i tried passing values using object weatherDataModel but nothing happens. i even declared a new object referring to container view class and used it in updateUI function to set label values but it won't work too.
I have no idea what to pass in place of string to get weather data through to next VC.
override func prepare(for segue: UIStoryboardSegue, sender for: Any?) {
if segue.identifier == "displayFullWeatherInfo"{
let destinationVC = segue.destination as! FullWeatherViewController
destinationVC.delegate = "\(weatherDataModel.pressure)"
....
....
Heres my WeatherDataModel Class:
import Foundation
class WeatherDataModel{
var city = ""
var temp = 0
var country = ""
var humidity = 0
}
in my main VC i have created weatherDataModel object and here's my updateWeatherInfo code:
func updateWeatherInfo(json : JSON){
if let tempDefault = json["data"][0]["temp"].double{
weatherDataModel.temp = Int(tempDefault)
weatherDataModel.city = json["data"][0["city_name"].stringValue
weatherDataModel.country = json["data"][0]["country_code"].stringValue
weatherDataModel.humidity = json["data"][0]["rh"].intValue
updateWeatherUI()
}
else{
currentLocation.text = "Not Available"
}
}
​
Create a property for your weather model object in your second VC FullWeatherViewController:
var weatherDataModel: WeatherDataModel! //your object
And in your first VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "displayFullWeatherInfo" {
let destinationVC = segue.destination as! FullWeatherViewController
destinationVC.weatherDataModel = weatherDataModel
}

String not being passed to next view controller from prepareForSegue

I have a push segue on my StoryBoard which is named toGuestVC.
I use that to segue to the next ViewController in my didSelectRowAtIndexPath method like so:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let username = followUsernameArray[indexPath.row]
performSegue(withIdentifier: SG_TO_GUEST_VIEW_CONTROLLER, sender: username)
}
Then in my prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == SG_TO_GUEST_VIEW_CONTROLLER {
if let nextVC = segue.destination as? GuestCollectionVC, let sender = sender as? String {
print("PRINTING NEXT VC: \(nextVC)") //This prints out the memory address. Not sure if this is what you meant by print nextVC.
nextVC.guestUser = sender
}
}
}
For some reason this line in my prepareForSegue is not running:
nextVC.guestUser = sender.username
When I try to print out the value guestUser in my nextViewController the value of guestUser is nil. But when I print out the value of sender in my prepareForSegue method it is not nil.
So is my sender value not being passed to the next ViewController? I can't find a solution to this problem any ideas?
GuestCollectionVC Implementation:
import UIKit
import Parse
private let reuseIdentifier = "Cell"
class GuestCollectionVC: UICollectionViewController {
var guestUser: String!
override func viewDidLoad() {
super.viewDidLoad()
print("PRINTING SELF IN GuestCollectionVC: \(self)")
loadPosts()
}
func loadPosts() {
//Load posts query
let query = PFQuery(className: PF_POSTS_CLASS)
query.limit = postCount
//Getting error here below this comment when I use guestUser since it is nil
query.whereKey(PF_POSTS_USERNAME_COLUMN, equalTo: guestUser)
query.findObjectsInBackground { (result: [PFObject]?, error: Error?) -> Void in
if error == nil {
if let result = result {
self.uuidOfPosts.removeAll(keepingCapacity: false)
self.imageArrayOfPFFIle.removeAll(keepingCapacity: false)
for postObject in result {
if let uuid = postObject[PF_POSTS_UUID_COLUMN] as? String, let pic = postObject[PF_POSTS_PIC_COLUMN] as? PFFile {
self.uuidOfPosts.append(uuid)
self.imageArrayOfPFFIle.append(pic)
}
}
self.collectionView?.reloadData()
}
}else if error != nil {
print("ERROR FROM GUEST COLLECTION VC FROM loadPosts FUNCTION: \(error?.localizedDescription)")
}
}
}
}
So this is my implementation in the GuestViewController. In my loadPosts method where I used the variable guestUser I am getting the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
From console printing
PRINTING NEXT VC: "InstagramClone.GuestCollectionVC: 0x7a6c5cc0"
PRINTING SELF IN GuestCollectionVC: "InstagramClone.GuestCollectionVC: 0x7a6c5100"
it's now obvious that hidden unexpected instance of GuestCollectionVC was created. So, there are different errors occurs depending on order of this two objects invoke their viewDidLoad method (can be any order). Also there are can be other errors in nextVC viewDidLoad method, but this is other story for other question.
You got this problems because you created action segue, that works automatically on cell click (hidden view controller created), and at the same time you are perform this segue in code, creating second controller nextVC.
To solve issue, you should find and remove that segue and add new one, not action segue from some element of your view controller, but "empty" segue between view controllers. To create segue of this type you should select first view controller, hold "control" key and start dragging to next view controller from yellow square on top of first view controller (that symbol controller itself), choose "show", and set identifier.
Since I don't have the full context of the values within your tableView method I can only speculate. That said, the sender you're passing in should be the view controller:
performSegue(withIdentifier: "toGuestVC", sender: username)
You're passing in a value called username which looks to be a string value? It should be something like:
performSegue(withIdentifier: "toGuestVC", sender: self)
where self is your view controller. If you're passing in a string value to sender then in your prepareForSegue method then sender does not have a property called username sender actually is username. Therefore you should pass the value elsewhere:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let indexPath = self.tableView.indexPathForSelectedRow
if let nextVC = segue.destination as? GuestCollectionVC {
nextVC.guestUser = followUsernameArray[indexPath.row]
}
}

Passing VC data not working properly

When a user taps on an image in my application, I would like for that to send them to another view controller to give them more post details but I can't quite figure out how to transfer that image from one view controller to the next!
The code below, should they tap the image, should send them to the detailed view controller - this is done so in the else if statement.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if username != PFUser.currentUser()?.username {
let profileVC: UserProfileViewController = segue.destinationViewController as! UserProfileViewController
profileVC.usernameString = username
} else if (segue.identifier == "toPostDetail") {
var svc = segue.destinationViewController as! PostDetailsViewController
svc.toPass = // Call/pass the image
}
}
To retrieve all the posts in my database I use the following code, this runs perfectly fine but I need to get the postImage trasnfered to the next view controller.
postsArray[indexPath.row]["image"].getDataInBackgroundWithBlock { (data, error) -> Void in
if let downloadedImage = UIImage(data: data!) {
postCellObj.postImage.image = downloadedImage
postCellObj.postImage.layer.masksToBounds = true
postCellObj.postImage.layer.cornerRadius = 5.0
}
}
So what would I put in the svc.toPass in order for the image to be sent to the other view controller? Since this is an array of posts, would I have to do something with didSelectRowAtIndexPath or am I doing the right thing? I am also using Parse.com to get information to and from my databases.
You should pass the post rather than the image, so it would be something like
postsArray[indexPath.row]
How you get the indexPath is up to you. If the segue is triggered by a cell selection then you could store the indexPath in didSelectRowAtIndexPath, or you could pass the indexPath as the sender when you trigger the segue, or you could use indexPathForSelectedRow if it's retained during the segue process.

How to send Parse object field from one class to another?

I want to make such thing:
On one ViewControleer I'm making a query to Parse.com, where I'm sending objects fields to Label.Text. By clicking one button objects randomly changes, by clicking another one- next ViewController is opening. Just imagine Tinder - on the first VC I swiping girls, on the new one chat is opening, with the girl's name in the head of the NavigatorItem
So I want to send Object Field "Name" that I'm using in that view to another without other query.
I don't know, whether I can do it via segue, or protocol. Can U somehow help me with implementation?
here is the code of my random function
func retriveJobData() {
var query: PFQuery = PFQuery(className: "Jobs")
query.getObjectInBackgroundWithId("AUeuvj0zk2") {
(newJobObject: PFObject?, error: NSError?) -> Void in
if error == nil && newJobObject != nil {
println(newJobObject)
if let newJobObject = newJobObject {
self.PrcieTextField.text = newJobObject["jobPrice"] as? String
self.DateTextField.text = newJobObject["jobDate"] as? String
self.DescriptionTextField.text = newJobObject["jobDescription"] as? String
}
} else {
println(error)
}
}
}
I want to send newJobObject["jobName"] to NavigatorItemName of another ViewController
you can override prepareForSegue for this purpose:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "yourSegueIdentifier") {
// pass data to next view
}
}
Assuming you have some method that triggers a push to the new viewController and that you're using the storyboard, call performSegue using the identifier you set up in the storyboard
#IBAction func buttonPressed(sender: UIButton!) {
performSegueWithIdentifier("identifier", sender: nil)
}
Then override prepareForSegue and pass in the string
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "identifier" {
let controller = segue.destinationViewController as! ViewController
controller.jobName = someWayThatYouRetrieveNewJobObjectName
}
Then in ViewController ensure you have a property for jobName
var jobName:String! //declare this as an optional instead if needed
And set the navigation title
navigationItem.title = jobName

Resources