How to send multiple variables through segue - ios

How can I send multiple variables through a segue in Swift? The QBBust gets sent over fine and prints on the view controller, but the QBName doesn't get sent over for some reason. Can anyone spot why?
if let send = sender as? Double{
destination.QBBust = send
}
if let sent = sender as? String{
destination.QBName = sent
}
}
}
private var _QBName:String!
var QBName: String{
get{
return _QBName
} set {
_QBName = newValue
}
}
private var _QBBust:Double!
var QBBust: Double {
get {
return _QBBust
} set{
_QBBust = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
let bust = String(Int(_QBBust))
QBBustLabel.text = "\(bust)%"
QBNameLabel.text = _QBName
}
This next part is in the button function that triggers the segue
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBName)

As in Tiago's answer, you can create a new struct or class which has QBName and QBBust properties. In addition, you can also use tuple in Swift.
This is an example:
in Destination ViewController
declare var QBInfo:(name: String, bust: Double)?
and in the button function that triggers the segue
let QBInfo = (name: QBName, bust: QBBust)
performSegue(withIdentifier: "QBResultVC", sender: QBBust)
then in prepareForSegue:sender:method
destination.QBInfo = QBInfo

This question is duplicate, but you can create a Struct or new Class, and storage your data how properties and send the 'transport' object in segue.
For detail, look this answser:
Swift sending Multiple Objects to View Controller

Related

How to store user data as string, and load that string in viewDidLoad

I am trying to load a value that has been inputted by the user in the viewDidLoad via a String. I am using UserDefaults to save the users value that they input into a UITextField (userValue), I then save this to the String 'search'. I am able to print out the value of search in the GoButton function, and it works fine, but when I load my ViewController as new, the value of 'search' is equal to nil. The aim here is to have the users previous search saved, and loaded into the UITextField (that is used as a search box) upon loading the ViewController.
Code Below:
class ViewController: UIViewController {
#IBOutlet weak var userValue: UITextField!
var search: String!
}
viewDidLoad:
override func viewDidLoad() {
if (search != nil)
{
userValue.text! = String (search)
}
}
Button Function:
#IBAction func GoButton(_ sender: Any) {
let userSearch: String = userValue.text!
let perference = UserDefaults.standard
perference.set(userSearch, forKey: "hello")
perference.value(forKey: "hello")
let value = perference.value(forKey: "hello") as! String
search = value
print (search) // <<this works, it prints out the users search value
}
#VishalSharma has the right idea, but the code should probably look more like…
override func viewDidLoad() {
super.viewDidLoad()
if let search = UserDefaults.standard.string(forKey: "hello") {
userValue.text = search
}
}
or even more simply…
userValue.text = UserDefaults.standard.string(forKey: "hello")
When you load, search is effectively nil.
So either you read userDefaults in viewDidload or you come through a segue: then you can load search in the prepare.
I've always found it convenient and useful to store all UserDefault properties as an extension within the same file along with their getters and setters. It is far easier to maintain, use and read. by using the #function keyword for the key you are referencing the variable's name and not a string that can be accidentally changed somewhere else in code.
UserDefaults.swift
import Foundation
// An Extension to consolidate and manage user defaults.
extension UserDefaults {
/// A value Indicating if the user has finished account setup.
/// - Returns: Bool
var finishedAcountSetup: Bool {
get { return bool(forKey: #function) }
set { set(newValue, forKey: #function) }
}
/// The hello text at the start of the application.
/// - Returns: String?
var helloText: String? {
get { return string(forKey: #function) }
set {set(newValue, forKey: #function) }
}
//etc...
}
When you use these values reference the standard settings:
//Setting
UserDefaults.standard.helloText = "Updated Hello Text"
// Getting
// for non-optional value you can just get:
let didCompleteSetup = UserDefaults.standard.finishedAcountSetup
// Otherwise, safely unwrap the value with `if-let-else` so you can set a default value.
if let text = UserDefaults.standard.helloText {
// Ensure there is text to set, otherwise use the default
label.text = text
} else {
// helloText is nil, set the default
label.text = "Some Default Value"
}
obviously, it provides nil because when view controller load the search is nil try this.
let perference = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
if (perference.value(forKey: "hello") != nil) {
search = perference.value(forKey: "hello") as! String
userValue.text! = String (search)
}
}

Sending a variable to be modified in another class?

How can one send a variable from a viewcontroller to be modified by another viewcontroller?
I've tried setting the variable in the performSegue, but it does not get modified.
Sample code:
class VC1: ViewController{
var myVar: MyVar
....
prepare(for segue:...) {
let nextVC = segue.destination as! VC2
nextVC.var = myVar
}
....
}
class VC2: ViewController {
var var: MyVar
....
override func viewDidLoad() {
super.viewDidLoad()
var = MyVar("newValue")
}
}
After this code is executed, the value of myVar in VC1 is not changed to the new value. I believe it only gets a shallow copy of myVar, not a deep copy.
Is what I want achievable in swift?
Classes in Swift are pass by reference, whereas structs are pass by value. Assuming that MyVar is a class, you need to modify the properties of the class, ie:
myVar.property = "xyz"
instead of defining a new instance of the class as you have done in your question.
When you set var = MyVar("newValue") it assign new instance to var.
Examine the results from the following code in a playground. It should give you more insight into what you should expect, without the complication of segues and controllers.
class Variable {
var name:String
init(_ nameString:String) {
name = nameString
}
}
class Sender {
var myVar = Variable("Start name")
func showChanges() {
let receiver = Receiver(myVar)
print(myVar.name)
receiver.modify()
print(myVar.name)
receiver.replace()
print(myVar.name)
}
}
class Receiver {
var received: Variable
init(_ variable:Variable) {
received = variable
}
func modify() {
received.name = "Changed name"
}
func replace() {
received = Variable("New variable")
}
}
let s = Sender()
s.showChanges()

UserDefault not being saved when going back to viewcontroller 1 and then back to viewcontroller 2

Okay so I have two view controllers. One view controller loads up all the cells that is on my Plist and the second view controller opens up the cell and shows you the description. For example:
View Controller 1:
Dog
Cat
Mouse
Click on Dog cell it will take you to View Controller 2:
Dog goes Woof.
view controller 1 is written:
ovverride func prepare(for segue: UIStoryBoardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let animals: Animals
if isFiltering() {
animals = filteredData[indexPath.row]
}
else {
animals = originalData[indexPath.row]
}
let controller = (segue.destination as! UINavigationController).topViewController as! SecondViewController
controller.detailedAnimals = animals
controller.navigationItem.leftBarButtonItem = splitViewController?.displayModeButtonItem
contrller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
this is what i wrote in viewcontroller 2 updated
var isFavorite : Bool = false
#IBAction func addToFav(_ sender:UIButton) {
isFavorite = !isFavorite
UpdateButtonAppearance()
saveData()
}
private func UpdateButtonAppearance(){
if isFavorite{
let image = UIImage(named: "addFav")
favButton.setImage(image, for: . normal)
savedData()
}
else {
let image = UIImage(named: "addFavFilled")
favButton.setImage(image, for: . normal)
savedData()
}
}
ovveride func viewDidLoad(){
UpdateButtonAppearance()
saveData()
}
//updated code
func getFilePath () -> String {
var path: [AnyObject] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true) as [AnyObject]
let documentsDirectory: String = path[0] as! String
let filepath = documentsDirectory.appending("Animals.plist")
return filepath
}
func saveData(){
let myDict : NSMutableDictionary = NSMutableDictionary()
myDict["fav"] = NSNumber(booleanLiteral: isFavorite)
myDict.write(toFile: self.getFilePath(), atomically: true)
}
func getBool(){
if FileManager.default.fileExists(atPath: self.getFilePath()) {
var myDict = NSDictionary(contentsOfFile: self.getFilePath()) as!
[String:AnyObject]
let myBool: Bool = myDict["fav"]!.boolValue
isFavorite = myBool
}
I saw a tutorial on how to change the bool in the Plist and wrote it this way. The code compiles but I don't think it is changing the bool value. So on my Animals Plist i have a Item 0 type dictionary, first key is called Animal, type is string, value is "dog" and second key is Description, type is string, value is "dog goes woof" and third key is called fav, type is Bool and value is No for now but I am trying to change this value to Yes but it is not working. Also thank you so much for your comment it was easy to follow and easy to understand.
The star is not filled when you go back to the 2nd view controller because you set the images in the #IBAction func addToFav(_ sender:UIButton) method. That is when you tap the button.
You should have that part of the code in another method that you also call in didLoad().
2nd View Controller should be something like this:
var isFavorite = UserDefaults.standard.bool(forKey: "isFavorite")
func didLoad() {
super.didLoad()
updateButtonAppearance()
}
private updateButtonAppearance() {
if isFavorite {
let image = UIImage(named: "addFavFilled")
button.setImage(image, for: . normal)
}
else {
let image = UIImage(named: "addFav")
button.setImage(image, for: . normal)
}
}
#IBAction func addToFav(_ sender:UIButton) {
isFavorite = !isFavorite
UserDefaults.standard.set(isFavorite, forKey: "isFavorite")
updateButtonAppearance()
}
Also the code could be improved to not save the variable in UserDefaults in addToFav method, but whenever isFavourite is changed. Maybe you will later want to change the state of isFavourite in another method which will require code duplication.
Also note that you save a value for all pets in UserDefaults under isFavourite. That means if you favor one pet, all other pets will be favored and vice versa. Consider replacing the bool value in user defaults with a dictionary that has keys for each pet and booleans as values.

How to send MSMessage in Messages Extension?

I want to implement an imessage app, however being new to the messages framework and iMessage apps being such a new thing there aren't many resources. So I am following the WWDC video and using Apples providing sample app for a guide.
I have three views, the MessageViewController which handles pretty much all the functionality and then a CreateViewController and a DetailsViewController.
I am simply trying to create an MSMessage from the CreateViewController and display in the DetailsViewController.. then add to the data.
However I get a crash when trying to create the data.
#IBAction func createAction(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateViewControllerDelegate)
}
The data type I am trying to pass is the dictionary from a struct:
struct data {
var title: String!
var date: Date!
var dictionary = ["title" : String(), "Array1" : [String](), "Array2" : [String]() ] as [String : Any]
}
So here's how things are set up;
MessagesViewController
class MessagesViewController: MSMessagesAppViewController, {
// MARK: Responsible for create list button
func composeMessage(for data: dataItem) {
let messageCaption = NSLocalizedString("Let's make", comment: "")
let dictionary = data.dictionary
func queryItems(dictionary: [String:String]) -> [URLQueryItem] {
return dictionary.map {
URLQueryItem(name: $0, value: $1)
}
}
var components = URLComponents()
components.queryItems = queryItems(dictionary: dictionary as! [String : String])
let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "messages-layout-1.png")!
layout.caption = messageCaption
let message = MSMessage()
message.url = components.url!
message.layout = layout
message.accessibilityLabel = messageCaption
guard let conversation = activeConversation else { fatalError("Expected Convo") }
conversation.insert(message) { error in
if let error = error {
print(error)
}
}
}
}
extension MessagesViewController: CreateViewControllerDelegate {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate) {
//CreatesNewDataItem
composeMessage(for: dataItem())
}
}
CreateViewController
/**
A delegate protocol for the `CreateViewController` class.
*/
protocol CreateViewControllerDelegate : class {
func createViewControllerDidSelectAdd(_ controller: CreateViewControllerDelegate)
}
class CreateViewController: UIViewController {
static let storyboardIdentifier = "CreateViewController"
weak var delegate: CreateViewControllerDelegate?
#IBAction func create(_ sender: AnyObject) {
//present full screen for create list
self.delegate?.createViewControllerDidSelectAdd(self as! CreateListViewControllerDelegate)
}
}
Would someone show where I am going wrong and how I can send a MSMessage? If I am able to send the message I should then be able to receive and resend.
One issue I see, without being able to debug this myself:
you are setting your components.queryItems to your dictionary var cast as [String:String], but the dictionary returned from data.dictionary is not a [String:String], but a [String:Any]
In particular, dictionary["Array1"] is an array of Strings, not a single string. Same for dictionary["Array2"]. URLQueryItem expects to be given two strings in its init(), but you're trying to put a string and an array of strings in (though I'm not sure that you're actually getting to that line in your queryItems(dictionary:) method.
Of course, your dataItem.dictionary is returning a dictionary with 4 empty values. I'm not sure that's what you want.

Firebase restoring initial values in textfields after changing text value

I am practicing iOS (Swift) with Firebase. the first viewcontroller retrieves all the records from firebase db and populate the tableView from an array. when the user selects an item from that tableview a new viewcontroller pops up segueing the object from the listView viewcontroller to the detail viewcontroller. data is populated to the fields successfully!
however when i try to update any of the textfield, the moment i switch to another textfield the initial value is restored in the edited textfield.
I have tried to removeAllObservers... but nothing worked. i even removed "import Firebase" and all associated objects and still it restores the initial value.
am i missing any concept here?
this is the code from the ListViewController:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated);
self.activityIndicator.startAnimating();
self.observeIngrdUpdates = ref.observeEventType(.Value, withBlock: { (snapshot) in
self.ingredients.removeAll();
for child in snapshot.children {
var nutrition:String = "";
var type:Int = 0;
var desc:String = "";
var img:String = "";
var name:String = "";
var price:Double = 0.0;
if let _name = child.value["IngredientName"] as? String {
name = _name;
}
if let _nutrition = child.value["NutritionFacts"] as? String {
nutrition = _nutrition;
}
if let _type = child.value["IngredientType"] as? Int {
type = _type;
}
if let _desc = child.value["UnitDescription"] as? String {
desc = _desc;
}
if let _img = child.value["IngredientImage"] as? String {
img = _img;
}
if let _price = child.value["IngredientPrice"] as? Double {
price = _price;
}
let ingredient = Ingredient(name: name, type: type, image: img, unitDesc: desc, nutritionFacts: nutrition, price: price);
ingredient.key = child.key;
self.ingredients.append(ingredient);
}
self.tableView.reloadData();
})
}
and the PrepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! EditIngredientVC;
if segue.identifier?.compare(SEGUE_EDIT_INGREDIENTS) == .OrderedSame {
destinationVC.ingredient = ingredients[tableView.indexPathForSelectedRow!.row];
destinationVC.controllerTitle = "Edit";
} else if segue.identifier?.compare(SEGUE_ADD_INGREDIENT) == .OrderedSame {
destinationVC.controllerTitle = "New";
}
}
this is the code for populating the fields in DetailViewController:
override func viewDidLayoutSubviews() {
lblControllerTitle.text = controllerTitle;
if controllerTitle?.localizedCaseInsensitiveCompare("NEW") == .OrderedSame {
self.segVegFru.selectedSegmentIndex = 0;
} else {
if ingredient != nil {
self.txtIngredientName.text = ingredient!.name;
self.txtUnitDesc.text = ingredient!.unitDesc;
self.segVegFru.selectedSegmentIndex = ingredient!.typeInt;
self.txtNutritionFacts.text = ingredient!.nutritionFacts;
self.txtPrice.text = "\(ingredient!.price)";
}
}
}
Thank you all for your help.
It's probably because you are putting your textField populating code, in viewDidLayoutSubviews() method.
This method will be triggered every time the layout of a views changes in viewcontroller.
Move it to viewDidLoad() and it should be fine.
The reason that it's being reverted to the previous value is because. You are populating the textField.text from the "ingredient" object. The ingredient will have the same value retained that you passed from previous view controller. Until you mutate it at some point.
And by the way Firebase is very cool I like it too :)

Resources