Passing data between view controllers through segue - ios

I have a MapViewController with a prepareForSegue(_:sender:)method, which I intend to use to send data to LandmarkTableViewController, and is called when a button is pressed.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if let identifier = segue.identifier {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
switch identifier {
case "showLibrary" : landmarkvc.passedLandmark = library // pass data to LandmarkTableViewController
case "showBank" : landmarkvc.passedLandmark = bank // pass data to LandmarkTableViewController
default : break
}
}
}
}
The LandmarkTableViewController is properly set up to display the String array properties, with one String on each row. So what I intend to do is pass the appropriate data for the table to properties according to which button was pressed, and let LandmarkTableViewController display the corresponding properties.
class LandmarkTableViewController: UITableViewController {
var properties = [String]()
var passedLandmark = Landmark(name: "temp", properties: ["temp"]) // initially set to default value
override func viewDidLoad() {
super.viewDidLoad()
loadSampleProperties()
}
func loadSampleProperties() {
self.properties = passedLandmark!.properties
}
// other methods....
}
class Landmark {
var name: String
var properties: [String]
init?(name: String, properties: [String]) {
self.name = name
self.properties = properties
// Initialization should fail if there is no name or if there is no property.
if name.isEmpty || properties.isEmpty {
return nil
}
}
However, when I run the code, only temp is displayed in the table view. I've been stuck on this for a long time now, so any help is much appreciated!
Edit: loadData() inside of viewDidLoad() is changed to the correct loadSampleProperties(). I made an error while posting the code to the question.

I think this should solve your problem if not double check your identifiers
and you can make sure to data passing with adding print(passedLandmark) to viewDidLoad() or breakpoint to make sure you getting the data
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationvc = segue.destinationViewController
if let landmarkvc = destinationvc as? LandmarkTableViewController {
if segue.identifier == "showLibrary" {
let library = Landmark(name: "Run Run Shaw Library", properties: ["Chinese Kanji", "Gray", "Green Plants"])
landmarkvc.passedLandmark = library
}
if segue.identifier == "showBank" {
let bank = Landmark(name: "Hang Seng Bank", properties: ["Chinese Kanji", "Green"])
landmarkvc.passedLandmark = bank
}
}
}
Hope this will helps

Code is missing from your quote, so I can't be sure, but I assume your loadData() method is the one that reloads the table view data with Landmark you've passed in prepareForSegue. If that is the case:
viewDidLoad() is called before prepareForSegue, so that all the views and elements of the destinationViewController are loaded and ready to use. Thus, in your case, the table view is loaded with your "temp" data and nothing makes it reload when you set the proper one.
You have two options:
You could call loadData()/reloadData() in viewWillAppear for example, which is called after prepareForSegue(). Bare in mind that viewWillAppear will possibly be called again in some other navigation.
Otherwise, you could instantiate and present/push the new controller in your parent view controller, instead of using the segue.

Related

Swift. Passing persistent data from one View Controller to another and storing within a label array

I am using a segue to go from View Controller 1 to View Controller 2. View Controller 1 has a button that sets the persistent data when it is clicked on:
I declare a global var for user default:
let userDefault = UserDefaults()
Here is my button to set the user default to a string with text values from labels:
#IBAction func saving(_ sender: UIButton) {
let savedText = "Gallon \(gallonTextFieldOutlet.text) is equal to Litre \(litreTextFieldOutlet.text) is equal to Pint \(pintTextFieldOutlet.text)"
userDefault.setValue(savedText, forKey: "SavedConversion")
}
I then get a reference to View Controller 2 and pass this user default when the user goes from View Controller 1 to View Controller 2 via a segue:
// in view controller 2: reference to get persistent data
var volumeDataOne:String?
// in view controller 2: instantiation of my queue class to use methods
var queue = Queue<String>()
// segue action in view controller 1
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "savedVolumeData"
{
let historyVC:VolumeHistoryViewController = segue.destination as! VolumeHistoryViewController
if let value = userDefault.value(forKey: "SavedConversion") as? String {
historyVC.volumeDataOne = value
}
}
I get this in the View Controller 2 and I am trying to set this to three labels that I have in this View Controller:
func DisplayVolumeHistory() {
let labelArray = [volumeDataLabelOutlet, volumeDataLabelTwoOutlet, volumeDataLabelThreeOutlet]
if let bindingOptional = volumeDataOne
{
for index in 0..<labelArray.count
{
queue.enqueue(val: bindingOptional)
labelArray[index]?.text = queue.arr[index]
}
}
}
In my specification, I have been told that the data needs to be persistent and that only the last five data can be stored at one time. So I have a class that I have called Queue which is referenced in this function. This function gets called on the viewDidLoad of the View Controller 2.
override func viewDidLoad() {
super.viewDidLoad()
//Debug ...
volumeDataLabelOutlet.text = "na"
volumeDataLabelTwoOutlet.text = "na 2"
volumeDataLabelThreeOutlet.text = "na 3"
//...
DisplayVolumeHistory()
// Do any additional setup after loading the view.
}
I have tested my Queue class in a Playground and it works as expected. The Queue class can be seen here:
class Queue {
var arr = [T]()
func enqueue(val: T){
if(arr.count < 3) {
arr.append(val)
} else {
for i in 0..<arr.count-1 {
arr[i] = arr[i + 1]
}
arr[arr.count - 1] = val
}
}
func dequeue() -> (T?){
if (arr.isEmpty){
return nil
} else {
return arr.remove(at: 0)
}
}
}
Here is my issue that I cannot seem to figure out. In View Controller 2, all of the three labels will have persistent data, but they will all be of the same data,
For example, if I have data as follows:
DATA 1: 555
DATA 2: 700
DATA 3: 62
I would want:
LABEL 1 --> 555
LABEL 2 --> 700
LABEL 3 --> 62
However, currently it will be:
LABEL 1 --> 62
LABEL 2 --> 62
LABEL 3 --> 62
I am unsure as to why debugging. I believe it is because my persistent data in my View Controller 1 is only taking a string, which the Dictionary is overriding as I use the same key.
However, I looked at the documentation and trying to use a user default array did not solve my issue and I am unsure as to what is causing this problem.
I appreciate any guidance and help to try to solve this issue.
Thanks
You are right on your comment,
which the Dictionary is overriding as I use the same key
Each time you tap that button, you are overriding the value with the new one. So you should be seeing always on your v2, the last one.
So probably you should store an array instead of a String.
//on v1
var values = [String]()
#IBAction func saving(_ sender: UIButton) {
let savedText = "Gallon \(gallonTextFieldOutlet.text) is equal to Litre \(litreTextFieldOutlet.text) is equal to Pint \(pintTextFieldOutlet.text)"
values.append(savedText)
UserDefaults.standard.setValue(values, forKey: "SavedConversion")
}
You will pass it to v2 as you are doing now
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "savedVolumeData"
{
let historyVC:VolumeHistoryViewController = segue.destination as! VolumeHistoryViewController
if let values = userDefault.value(forKey: "SavedConversion") as? [String] {
historyVC.volumeDataOne = values //volumeDataOne now needs to be an [String]
}
}
Then on V2, go through your array and labels.

Sending data to another view controller

This is a learning app that I am making for fun, I have been stuck here for 2 days.
I have two views setup that I use to send data that the user will pick to the other one (they are named AddCoinVC and MainVC).
In AddCoinVC, the sending is performed when the user clicks on the button
let vc = MainViewController()
vc.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
performSegue(withIdentifier: "backToMain", sender: self)
I have setup a breakpoint at this point and printing vc.coinArray prints me the correct value =
($R0 = 1 value { (coinName = "Test", coinSymbol = "Test", coinAmount = "0")
}
But when I go to my other breakpoint at MainVC, it displays 0 value.
var coinArray = [CoinWallet]()
This is the var that I use, the default in MainVC is CoinWallet which is empty when first loading the app. This is the custom Class.
class CoinWallet {
var coinName:String = ""
var coinSymbol:String = ""
var coinAmount:String = ""
init(coinName: String, coinSymbol:String, coinAmount: String) {
self.coinName = coinName
self.coinSymbol = coinSymbol
self.coinAmount = coinAmount
}
}
When the segue and the sending is performed from AddCoinVC to MainVC 'coinArray' should have this value sent to it.
Why would be the value empty if vc.coinArray has 1 value?
The error occurs because MainViewController() does not return the view controller you expect. It's a new blank instance which is not the instance in the storyboard.
Since you are performing a segue anyway, pass the CoinWallet instance as sender parameter in performSegue
let coin = CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0")
performSegue(withIdentifier: "backToMain", sender: coin)
Then implement prepare(for segue and use the destination property as reference to the main view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "backToMain" {
let coin = sender as! CoinWallet
let mainViewController = segue.destination as! MainViewController
mainViewController.coinArray.append(coin)
}
}
You should add this method in your AddCoinVC and send data as below,
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let mainVC = segue.destination as? MainViewController {
mainVC.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
}
}
In the below lines of code, you are just creating a new instance that is not the viewController being segued.
let vc = MainViewController()
vc.coinArray.append(CoinWallet(coinName: "Test", coinSymbol: "Test", coinAmount: "0"))
You should only perform the segue on button click as below and set any data inside the above method.
performSegue(withIdentifier: "backToMain", sender: self)

Cannot take values from other view controller Swift

I want to take user settings details from this view controller and read these details to the previous view controller. I have tried many different ways, but I cannot take values until I visit this view controller
I have tried first method from this page Pass Data Tutorial
This method is also not working. I think it is very simple, but I cannot figure out the right way to do it.
class SetConvViewController: UIViewController {
var engS = "engS"
#IBOutlet weak var swithEnglish: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let eng2 = defaults.value(forKey: engS)
{
swithEnglish.isOn = eng2 as! Bool
}
}
let defaults = UserDefaults.standard
#IBAction func switchEng(_ sender: UISwitch) {
defaults.set(sender.isOn, forKey: engS)
}
}
If I understand you correctly from this part - „but I cannot take values until I visit this view controller” - your problem lies with the fact, that until you visit your settings, there is no value for them in UserDefaults.
If you are reading them using getObject(forKey:) method, I’d recommend you to switch to using getBool(forKey:), since it will return false even if the value has not been set yet for that key ( docs )
Anyhow, if you want to set some default/initial values you can do so in your didFinishLaunching method in AppDelegate :
if UserDefaults.standard.object(forKey: „engS”) == nil {
// the value has not been set yet, assign a default value
}
I’ve also noticed in your code that you used value(forKey:) - you should not do that on UserDefaults - this is an excellent answer as to why - What is the difference between object(forKey:) and value(forKey:) in UserDefaults?.
On a side note, if you are using a class from iOS SDK for the first time, I highly recommend looking through its docs - they are well written and will provide you with general understanding as to what is possible.
I would recommend you to store this kind of data as a static field in some object to be able to read it from any place. e.g.
class AppController{
static var userDefaults = UserDefaults.standard
}
and then you can save it in your SetConvViewController like
#IBAction func switchEng(_ sender: UISwitch) {
AppController.userDefaults.set(sender.isOn, forKey: engS)
}
and after that you can just read it from any other view controller just by calling
AppController.userDefaults
Using segues you can set to any destination whether it be next vc or previous:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "PreviousVC" {
if let prevVC = segue.destination as? PreviousViewController {
//Your previous vc should have your storage variable.
prevVC.value = self.value
}
}
If you're presenting the view controller:
Destination vc:
//If using storyboard...
let destVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DestinationViewController") as! DestinationViewController
destVC.value = self.value
self.present(destVC, animated: true, completion: nil)
Previous vc:
weak var prevVC = self.presentingViewController as? PreviousViewController
if let prevVC = prevVC {
prevVC.value = self.value
}

How to force asynchronously save a constant subclass?

Edit 1: I've restructured my ViewControllers to make it easier to get what I want done.
Edit 2: I realized something major was missing while adding notes to my code, another function overrides the first segue.
This ViewController is where the annotation is created; all I need from this view is for the touchMapCoordinates to be transferred to the other ViewController so I can save the PFGeoPoint in an array.
Edit 3
After long work on understanding what is going on and simplifying the code, i've came down to the final conclusion based off of Swift- variable not initialized before use (but it's not used) , that the current method that I'm trying to use will not work in any case or scenario due to it saving Asynchronously. If anyone knows a work around, then you have officially done something that hasn't been done before :).
Error that is showing up is
Constant 'boi' used before being initialized
Subclass that is declared in Appdata to be used anywhere within the project
import Foundation
import Parse
import MapKit
class MyAnnotation: PFObject, PFSubclassing, MKAnnotation {
// MARK: - Properties
#NSManaged var location: PFGeoPoint
// MARK: - Initializers
init(coordinate: CLLocationCoordinate2D) {
super.init()
self.location = PFGeoPoint(latitude: coordinate.latitude, longitude: coordinate.longitude)
print(location)
}
override class func initialize() {
struct Static {
static var onceToken : dispatch_once_t = 0;
}
dispatch_once(&Static.onceToken) {
self.registerSubclass()
}
}
// MARK: - PFSubclassing protocol
static func parseClassName() -> String {
return "AnnotationPins"
}
// MARK: - MKAnnotation protocol
var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2DMake(location.latitude, location.longitude)
}
var title: String? = "Start Topic"
}
Where the code will all be saved asynchronously together
} else {
let imageData = UIImagePNGRepresentation(self.galleryCameraImage.image!)
let parseImageFile = PFFile(name: "upload_image.png", data: imageData!)
let boi : MyAnnotation
let textTitleandText = PFObject(className: "AnnotationPins")
textTitleandText["textTopic"] = userTopic.text
textTitleandText["textInformation"] = userText.text
textTitleandText["userUploader"] = PFUser.currentUser()
textTitleandText["imageFile"] = parseImageFile!
textTitleandText["location"] = boi.location
textTitleandText.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
if error == nil {
If anyone could help it would be really appreciated!
Over ride prepareForSegue method like below.
override func prepareForSegue(segue: UIStoryboardSegue, sender:
AnyObject?) {
if segue.identifier == "SegueID" {
let destinationVC = segue.destinationViewController as! DestinationViewController
// Create property in destinationView controller & assign required data from here.
}
}
Hope it helps.
Lets treat your Location data as a normal data to be transferred through segues.
You can use this method to configure your destination View controller variable(same type) that will hold your location data.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
//check your segue name
if segue.identifier == "YourSegueIdentifier" {
let destinationVC = segue.destinationViewController as! YourDestinationViewController
destinationVC.locationVariableInDestinationVC = locationVariableInCurrentVC
}
}
Above is the simplest way to pass data via segue, you can use the same approach for your location data too.
Hope that helps!!
Update: Based on your updated code
Move func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {} out of your handleLongPress function. PrepareForSegue function gets called automatically when there is any navigation happening through segues..
If you want to initiate a segue navigation programatically then assign a identifier to the segue and just call self.performSegueWithIdentifier("YourSegueIdentifier", sender: nil)

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