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
Related
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
}
I'm trying to do an iOS app with two auth level using firebase.
The second auth must be validate by insert on a text field a code witch is located in databse.
json root
//
// AccessoTerzoLivello.swift
// amesci
//
// Created by Gianluca Caliendo on 07/07/17.
// Copyright © 2017 Amesci. All rights reserved.
//
import UIKit
import Firebase
class AccessoTerzoLivello: UIViewController {
#IBOutlet weak var CodiceVolontarioTxt: UITextField!
#IBAction func Accedi(_ sender: UIButton) {
var rootRef: DatabaseReference!
rootRef = Database.database().reference()
var conditionalRef = rootRef.child("SchedaVolontari")
conditionalRef.observe(.value) {(snap: DataSnapshot) in }
if self.CodiceVolontarioTxt.text = snap
}
else {
let alert = UIAlertController(title: "Errore", message: "Codice volontario non valido", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
You are on the right way. There is a problem with your line:
if self.CodiceVolontarioTxt.text = snap
Snap is a dictionary which holds your codes, you can't compare it to your text input. First of all you have to get all the codes from it.
Here is some code. I haven't tested this but it will give you an idea. Replace the if else statement in your completion block with this:
// This is empty array which will contain all the codes
var codesArray: NSMutableArray = []
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? FIRDataSnapshot {
// Get code node key and save it to codes array
codes.add(child.key)
}
// Compare if inserted text matches any code from database
if codesArray.contains(self.CodiceVolontarioTxt.text) {
print("Code exists in Firebase")
} else {
print("Code does not exist in Firebase")
}
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
#IBAction func sendSweet(sender: UIBarButtonItem) {
var inputTextField: UITextField?
let alert = UIAlertController(title: "New sweet", message: "Enter a sweet", preferredStyle: .alert)
alert.addTextField { (textField: UITextField) in
textField.placeholder = "Your sweet"
inputTextField = textField
}
let sendAction = UIAlertAction(title: "Send", style: .default, handler: {
[weak self] (alertAction: UIAlertAction) in
guard let strongSelf = self else { return }
if inputTextField?.text != "" {
let newSweet = CKRecord(recordType: "Sweet")
newSweet["content"] = inputTextField?.text as CKRecordValue?
let publicData = CKContainer.default().publicCloudDatabase
publicData.save(newSweet, completionHandler: {
(record: CKRecord?, error: Error?) in
if error == nil {
// we want ui code to dispatch asychronously in main thread
DispatchQueue.main.async {
strongSelf.tableView.beginUpdates()
strongSelf.sweets.insert(newSweet, at: 0)
let indexPath = IndexPath(row: 0, section: 0)
strongSelf.tableView.insertRows(at: [indexPath], with: .top)
strongSelf.tableView.endUpdates()
}
} else {
if let error = error {
print(error.localizedDescription)
return
}
}
})
}
})
alert.addAction(sendAction)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
I have this callback hell, I want to know
Does the [weak self] & guard let strongSelf at very top of the callback hell prevent the strong reference cycle all out through the GCD's async callback. I read some other post at here, also one from a book, that said if the object I refer inside a callback can deinit successfully, it means a good sign for not having strong reference cycle, is it still true?
How to prevent this kind of callback hell, can you lead me to some reading material or topic I have missed? anything like javascript's promise chaining syntax?
As far as I can see there is no retain cycle and thus there is no need to weakify self. You certainly can do it in every block to be defensive.
There is no retain cycle because the instance (self) does not have reference to any of the closures. Especially to sendAction, as sendAction is declared inside of the sendSweet function.
class MyView: UIView {
let str = "some variable to have somsthing to use self with"
func foo() {
let ba = {
// no problem. The instance of MyView (self) does not hold a (strong) reference to ba
self.str.trimmingCharacters(in: CharacterSet.alphanumerics)
}
ba()
}
}
If you would move let sendAction = ... outside of the function as a property of the instance, you would have a reference cycle though. In this case the instance (self) would have a strong refrence to sendAction and the sendAction closure would have a strong reference to the instance (self):
self <-> { self. ...} aka sendAction.
class MyView: UIView {
let str = "asd"
// Problem.
// The instance of MyView (self) does hold a (strong) reference to ba ...
let ba: () -> Void
override init(frame: CGRect) {
super.init(frame: frame)
ba = {
// ... while ba holds a strong reference to the instance (self)
self.str.trimmingCharacters(in: CharacterSet.alphanumerics)
}
}
func foo() {
ba()
}
}
In this case you have to break the cycle by weakifying self within the closure, like you did.
How to prevent this kind of callback hell, can you lead me to some reading material
Checkout DispatchGroups.
(Apple Documentation)
Found a pretty neat solution to my problem #2
https://github.com/duemunk/Async
Example snippet:
Async.userInitiated {
return 10
}.background {
return "Score: \($0)"
}.main {
label.text = $0
}
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);
}
}