How to pass value from DetailView to TableViewList [duplicate] - ios

This question already has answers here:
Passing Data between View Controllers in Swift
(7 answers)
Closed 5 years ago.
I'm building a simple app with Swift 3. So I have a TableView List and a Detail View. So I have created, tow method to add items from Detail View to TableView List.
Detail.swift:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//se il pulsante cliccato è diverso da OK torno indietro
if sender as? NSObject != self.buttonOK{
return
}
let nomeLuce = self.textNomeLuce.text!
let pinArduino = Int16(self.textPinArduino.text!)
let tipoLuce = self.textTipoLuce.text!
//DEVO VERIFICARE SE SONO IN MODIFICA O SALVATAGGIO
if((self.nuovaLuce?.id)! > 0){
self.nuovaLuce?.descrizione = nomeLuce
self.nuovaLuce?.pin_arduino = pinArduino!
LuciKitCoreDataController.shared.update(updateLuci: self.nuovaLuce!)
}else if(nomeLuce.characters.count>0){
//ho inserito almeno un carattere
let idInsert = LuciKitCoreDataController.shared.addLuce(descrizione: nomeLuce, pin_arduino: Int(pinArduino!), id: (self.nuovaLuce?.id)!)
self.nuovaLuce?.descrizione = nomeLuce
self.nuovaLuce?.pin_arduino = pinArduino!
self.nuovaLuce?.id = idInsert
}else{
let alert = UIAlertController(title:"Attenzione", message: "Inserire un nome per la Luce", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated:true, completion: nil)
}
}
TableView.swift
#IBAction func tornaAllaLista(_ segue: UIStoryboardSegue){
do {
var vistaDettaglio: AggiungiLuceViewController = segue.source as! AggiungiLuceViewController
if(vistaDettaglio.nuovaLuce != nil){
self.listaLuci.append(vistaDettaglio.nuovaLuce!)
self.tabella.reloadData()
}else{
}
} catch let errore {
print("[CDC] problema tornaAllaLista")
print(" Stampo l'errore: \n \(errore) \n")
}
}
Now there is any way to pass at TableViewList some value as a Boolean value?
I want to pass for example this parameter
Boolean isNew = true | false
EDIT
I don't know if I have used a correct way. But I have insert this variables into Detail.swift class:
var isNew : Bool = true
In TableView.swift class I have used this code to read this information:
var vistaDettaglio: AggiungiLuceViewController = segue.source as! AggiungiLuceViewController
if(vistaDettaglio.nuovaLuce != nil){
//verifico se devo aggiungere un valore o lo devo aggiornare
if(vistaDettaglio.isNew){
self.listaLuci.append(vistaDettaglio.nuovaLuce!)
}else{
}
self.tabella.reloadData()
}

There is 2 ways to do this.
Delegate/Protocol
NotificationCenter
Delegate is perfect to pass value from Details to List, because delegate is used to 1 to 1 message passing, and NotificationCenter is used for broadcasting.
Here you can get example of it.
Pass data back to previous viewcontroller

Related

How to make a button modify another button functions

I have two UIViewController. In the first one, I have a button that adds some views, one at a time, to the main view. In the second one, I set up a store, so that when I press on a button I unlock some features of my app. Now, I perfectly know (I hope) how to handle the part where I make the VCs comunicate and I trigger some other easy functions, what I don't know is how to make the store button increase the button's functions.
WHAT I NEED:
Right now the button adds a maximum of 10 views (complete version). I want that before the user buys my app, he gets to add a maximum of 3 views and then, when he buys it, the function I already have (the one to add 10 views)starts to work and replaces the other one.
MAIN VIEW CONTROLLER
var messageArray = [UIView] ()
I attached all of my UIView from the storyboard and I appended them to my array in the viewDid load like this: messageArray.append(View1)
#IBAction func addMessageViewButton(_ sender: Any) {
let hiddenViews = messageArray.filter { $0.isHidden }
guard !hiddenViews.isEmpty else {
let sheet = UIAlertController(title: "max reached", message: nil, preferredStyle: .actionSheet)
let ok = UIAlertAction(title: "OK", style: .cancel, handler: nil)
let closeAll = UIAlertAction(title: "Close all", style: .destructive) { (addMessage) in
view1.isHidden = true
view2.isHidden = true
view3.isHidden = true
view4.isHidden = true
view5.isHidden = true
view6.isHidden = true
view7.isHidden = true
view8.isHidden = true
view9.isHidden = true
view10.isHidden = true
}
sheet.addAction(ok)
sheet.addAction(closeAll)
present(sheet, animated: true, completion: nil)
return
}
let randomHiddenView = hiddenViews.randomElement()
randomHiddenView?.isHidden = false
}
SHOP VIEW CONTROLLER
Here I won't post all of the code because it would be too much and of course unnecessary, since the important thing to know here is that there's a button and if the user presses it and he proceeds with the purchase, he will get the function I posted up here working instead of the one that allows him to have just 3 views.
func unlock() {
let appdelegate = UIApplication.shared.delegate
as! AppDelegate
appdelegate.viewController!.functionToHave10Views()
//viewControlled is declared in the app delegate like this ` var viewController: ViewController?`
//I know I don't physically have `functionToHave10Views()`, but I guess I'll turn the code of my button into a function, so just to be clear, I'm referring to that function.
buyButton.isEnabled = false
}
In your main view controller:
var isLocked = true
#IBAction func addMessageViewButton(_ sender: Any) {
if isLocked {
// Do something for when is locked
} else {
// Do something for when is unlocked
}
}
Then in your shop view controller:
func unlock() {
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.viewController!.isLocked = false
buyButton.isEnabled = false
}

Reloading TableView when a UIViewController is being dismissed?

The problem here is that I'm presenting EditCommentVC modally, over the current context of the CommentVC because I want to set the background of the UIView to semi-transparent. Now, on the EditCommentVC I have a UITextView that allows the user to edit their comment, along with 2 buttons - cancel (dismisses the EditCommentVC) and update that updates the new comment and push it to the database.
In term of code, everything is working, except that once the new comment is being pushed and EditCommentVC is being dismissed, the UITableView on CommentsVC with all the comments is not being reloaded to show the updated comments. Tried calling it from viewWillAppear() but it doesn't work.
How can I reload the data in the UITableView in this case?
#IBAction func updateTapped(_ sender: UIButton) {
guard let id = commentId else { return }
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
let commentVC = CommentVC()
commentVC.tableView.reloadData()
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
}
The code in the CommentVC where it transitions (and passes the id of the comment). CommentVC conforms to a CommentActionProtocol that passes the id of that comment:
extension CommentVC: CommentActionProtocol {
func presentActionSheet(for commentId: String) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let editAction = UIAlertAction(title: "Edit", style: .default) { _ in
self.performSegue(withIdentifier: "CommentVCToEditComment", sender: commentId)
}
actionSheet.addAction(editAction)
present(actionSheet, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
}
}
}
I see atleast 2 problems here:
You are creating a new CommentVC which you should not do if you want to update the tableView in the existing view controller.
Since you have mentioned that Api.Comment.updateComment is a an asynchronous call, you need to write the UI code to run on the main thread.
So first you need to have the instance of the commentVC in a variable inside this viewController. You can store the instance of the view controller from where you are presenting this view controller.
class EditCommentVC {
var commentVCdelegate: CommentVC!
// Rest of your code
}
Now you need to pass the reference commentVC in this variable when you are presenting the edit view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentVCToEditComment" {
let editCommentVC = segue.destination as! EditCommentVC
let commentId = sender as! String
editCommentVC.commentId = commentId
editCommentVC.commentVCdelegate = self
}
}
Now you need to use this reference to reload your tableView.
Api.Comment.updateComment(forCommentId: id, updatedComment: editTextView.text!, onSuccess: {
DispatchQueue.main.async {
commentVCdelegate.tableView.reloadData() // - this commentVC must be an instance that you store of the your commentVC that you created the first time
self.dismiss(animated: true, completion: nil)
}
}, onError: { error in
SVProgressHUD.showError(withStatus: error)
})
Well, i had this problem too, and the solution i found was to use Protocol. I would recommend you to search how to send data back to previous ViewController. That way, when you dismiss the EditCommentVC, you then send back a value(in my case i send true) to the previous ViewController(in your case, CommentVC), and then you'll have a function in CommentVC checking if the value is true and if it is, reload the TableView.
Here, let me show you an example of how i used (those are the names of my ViewControllers, functions and protocols, you can use whatever you want and send whatever data you want back):
In your CommentVC, you'll have something like this:
protocol esconderBlurProtocol {
func isEsconder(value: Bool)
}
class PalestranteVC: UIViewController,esconderBlurProtocol {
func isEsconder(value: Bool) {
if(value){
//here is where you can call your api again if you want and reload the data
tableView.reloadData()
}
}
}
Also, dont forget that you have to set the delegate of EditCommentVC, so do it when you're presenting EditCommentVC, like this:
let viewController = (self.storyboard?.instantiateViewController(withIdentifier: "DetalhePalestranteVC")) as! DetalhePalestranteVC
viewController.modalPresentationStyle = .overFullScreen
viewController.delegate = self
self.present(viewController, animated: true, completion: nil)
//replace **DetalhePalestranteVC** with your **EditCommentVC**
And in your EditCommentVC you'll have something like this:
class DetalhePalestranteVC: UIViewController {
var delegate: esconderBlurProtocol?
override func viewWillDisappear(_ animated: Bool) {
delegate?.isEsconder(value: true)
}
}
That way, everything you dismiss EditCommentVC, you'll send back True and reload the tableView.

How can I make a variable inside a function global?

I currently have the following function called saveRun()
func saveRun() {
let startLoc = locations[0]
let endLoc = locations[locations.count - 1]
let startLat = startLoc.coordinate.latitude
let startLong = startLoc.coordinate.longitude
let endLat = endLoc.coordinate.latitude
let endLong = endLoc.coordinate.longitude
//1. Create the alert controller
let alert = UIAlertController(title: "Save the Run", message: "Choose a name: ", preferredStyle: .alert)
//2. Add the text field
alert.addTextField { (textField) in
textField.text = ""
}
// 3. Grab the value from the text field, and print it when the user clicks OK
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak alert] (_) in
let textField = alert?.textFields![0] // Force unwrapping because we know it exists.
// Create name for run
let runName = textField?.text
let run = self.databaseRef.child(runName!)
let user = FIRAuth.auth()?.currentUser?.uid
// Enter run info into db
run.child("startLat").setValue(startLat)
run.child("startLong").setValue(startLong)
run.child("endLat").setValue(endLat)
run.child("endLong").setValue(endLong)
run.child("distance").setValue(self.distance)
run.child("time").setValue(self.seconds)
run.child("user").setValue(user)
// Enter locations into db
var i = 0
for location in self.locations {
run.child("locations").child("\(i)").child("lat").setValue(location.coordinate.latitude)
run.child("locations").child("\(i)").child("long").setValue(location.coordinate.longitude)
i = i + 1
self.performSegue(withIdentifier: DetailSegueName, sender: nil)
}
}))
// 4. Present the alert
self.present(alert, animated: true, completion: nil)
}
My problem is that I am trying to extract 'runName' from the action that I am adding when the user clicks 'Ok' on the alert controller and using it in the following function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let detailViewController = segue.destination as? DetailViewController {
detailViewController.runName = self.runName
}
}
When I try to print 'runName' in DetailViewController, the value of runName is nil. The issue I think is that I cannot set a global variable inside the action I have added as it is in a function. Is there any other way I can obtain this variable's value and use it outside of the function?
Class YourClassName:UIViewController {
var runName:String = "" // This will be global for your class
//Remove local decalration of runName variable
func saveRun() { // your function
alert.addAction(
//.....
self.runName = textfield?.text
)
}
}
Now you can use in whole class.
I solved this thanks to #DSDharma pointing out that even if 'runName' was set as a global variable, using it as a global variable inside of an alert function block required the 'self' keyword.
For example, before I had the following inside of the alert function block:
let runName = textField?.text
This needed to be changed to:
self.runName = textField?.text

How can read object from detail view? [duplicate]

This question already has an answer here:
How to pass value from DetailView to TableViewList [duplicate]
(1 answer)
Closed 5 years ago.
I have a problem with my Swift app. I used Swift 3 and Xcode 8.
I have implemented a TableViewController and DetailViewController.
So I want to add a new item and then refresh automatically the TableViewController.
So This is the code of TableViewController that I called when I click on OK button from DetailViewController.
#IBAction func tornaAllaLista(_ segue: UIStoryboardSegue){
do {
var vistaDettaglio: AggiungiLuceViewController = segue.source as! AggiungiLuceViewController
if(vistaDettaglio.nuovaLuce != nil){
//verifico se devo aggiungere un valore o lo devo aggiornare
print(vistaDettaglio.isNew)
if(vistaDettaglio.isNew){
self.listaLuci.append(vistaDettaglio.nuovaLuce!)
}else{
}
self.tabella.reloadData()
}
} catch let errore {
print("[CDC] problema tornaAllaLista")
print(" Stampo l'errore: \n \(errore) \n")
}
}
If the vistaDettaglio.nuovaLuce is not null I want to add this new Items in my TableView.
This is the code of DetailViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//se il pulsante cliccato è diverso da OK torno indietro
if sender as? NSObject != self.buttonOK{
return
}
let nomeLuce = self.textNomeLuce.text!
let pinArduino = Int16(self.textPinArduino.text!)
let tipoLuce = self.textTipoLuce.text!
self.nuovaLuce?.descrizione = nomeLuce
self.nuovaLuce?.pin_arduino = pinArduino!
self.nuovaLuce?.tipo_luce = tipoLuce
//DEVO VERIFICARE SE SONO IN MODIFICA O SALVATAGGIO
if(self.nuovaLuce != nil && (self.nuovaLuce?.id)! > 0){
self.isNew = false;
LuciKitCoreDataController.shared.update(updateLuci: self.nuovaLuce!)
}else if(nomeLuce.characters.count>0){
self.isNew = true
//ho inserito almeno un carattere
let idInsert = LuciKitCoreDataController.shared.addLuce(descrizione: nomeLuce, pin_arduino: Int(pinArduino!), id: -1 , tipoLuce: tipoLuce)
self.nuovaLuce?.descrizione = nomeLuce
self.nuovaLuce?.pin_arduino = pinArduino!
self.nuovaLuce?.tipo_luce = tipoLuce
self.nuovaLuce?.id = idInsert
}else{
let alert = UIAlertController(title:"Attenzione", message: "Inserire un nome per la Luce", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated:true, completion: nil)
}
}
So if I try to add a new Items, I have vistaDettaglio.nuovaLuce = NIL.
How can I fixed this problem?
Reactive Cocoa will work wonders for this exact task. Have a look into it.
If that seems slightly too technical, you could try using custom notifications and the Notification Centre, or you could try using KVO to determine as and when the items in the detail view change.

Xcode 7 + swift. "Use of local variable '_' before its declaration" Error

I get the above error when trying to create a function to check user inputs and store data. My project builds fine until I reach this function RegisterButtonTapped(). Does anyone have some structural or syntax changes that could get rid of this error?
#IBAction func RegisterButtonTapped(sender: AnyObject) {
let userEmail = userEmailTextField.text;
let userPassword = userEmailTextField.text;
let userRepeatPassword = userRepeatPasswordTextField.text;
// Check for empty fields
if(userEmail!.isEmpty || userPassword!.isEmpty || userRepeatPassword!.isEmpty){
displayAlertMessage("All fields are required");
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword){
displayAlertMessage("Passwords do not match");
return;
}
// Store Data
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userEmail");
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userPassword");
NSUserDefaults.standardUserDefaults().synchronize();
// Display Alert message with confirmation
var myAlert = UIAlertController(title:"Alert",message:"Registration is successful, thank you.",preferredStyle: UIAlertControllerStyle.Alert);
func displayAlertMessage(userMessage:String){
var myAlert = UIAlertController(title:"Alert",message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"ok",style: UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil);
} // END OF FUNCTION 'displayAlertMessage()'
} // END of FUNCTION 'RegisterButtonTapped()'
When you have a nested function, you have to declare it before you can call it. In this case, move the "displayAlertMessage(userMessage:String)" function above the "// Check for empty fields" comment, then it should compile.
You have declared displayAlertMessage after you are calling it, move the declaration of it near the top of RegisterButtonTapped() if you want to keep it as a nested function, otherwise move it out of RegisterButtonTapped().
Apart from that you have two variables both called myAlert, the first is useless, and you are saving userEmail as both the email and the password, also calling synchronize() is not required.
There are some details to get it running right. The displayAlertMessage was declared inside the register function after the calling and like that we get the warning. When you get success, you must call the function with the success message, and not declaring var myAlert like inside the alert function. And final detail: when getting the UITextFields values, you got email input field and set it to password value, so the validation will be wrong.
Here a sample of code that works great:
class ViewController: UIViewController {
#IBOutlet var userEmailTextField:UITextField!
#IBOutlet var userPassTextField:UITextField!
#IBOutlet var userRepeatPasswordTextField:UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func RegisterButtonTapped(sender:UIButton) {
let userEmail = userEmailTextField.text;
let userPassword = userPassTextField.text;
let userRepeatPassword = userRepeatPasswordTextField.text;
// Check for empty fields
if(userEmail!.isEmpty || userPassword!.isEmpty || userRepeatPassword!.isEmpty){
displayAlertMessage("All fields are required");
return;
}
// Check if passwords match
if(userPassword != userRepeatPassword){
displayAlertMessage("Passwords do not match");
return;
}
// Store Data
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userEmail");
NSUserDefaults.standardUserDefaults().setObject(userEmail, forKey: "userPassword");
NSUserDefaults.standardUserDefaults().synchronize();
displayAlertMessage("Registration is successful, thank you.")
}
// Display Alert message with confirmation
func displayAlertMessage(userMessage:String){
let myAlert = UIAlertController(title:"Alert",message: userMessage, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"ok",style: UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil);
}
}

Resources