How can read object from detail view? [duplicate] - ios

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.

Related

What is wrong with this UIAlertAction code? Won't allow a click/tap and move forward in the app

I'm enrolled in a Swift/iOS Bootcamp course on Udemy. I've not had any issues prior to this one with any of the code or lessons, but this has me stumped. The module is about setting up UIAlertController and UIAlertAction, and resetting a quiz app to the beginning of the quiz. The quiz works as it should (at this point at least), however, once the Alert pops up, I can't click on it. It doesn't recognize any of the taps or clicks or move forward to the function that should be called when the click is received.
https://imgur.com/Z7Oc6kU
I'm following the code as written in the course, but one of the issues could possibly be that the course is using an older version of Swift/Xcode than I am, but I'm not 100% positive. That could be causing some confusion.
However, I have tried to utilize the Apple API Documentation, at one point, copying all of their sample code and adjusting to fit my needs (in terms of function to call after 'OK'), but even that code didn't register a click. I've searched on Stack, altered code based on other forums, and nothing has worked.
func nextQuestion() {
if questionNumber <= allQuestions.list.count - 1 {
var nextQuestion = allQuestions.list[questionNumber]
questionLabel.text = nextQuestion.questionText
}
else {
let alert = UIAlertController(title: "Quiz Over", message: "You've Finished the Quiz, Press Restart to Start Over", preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Restart", style: .default) { (UIAlertAction) in
self.startOver()
}
alert.addAction(restartAction)
self.present(alert, animated: true, completion: nil)
}
}
As mentioned, the 'Restart' button on the Alert should force the quiz to restart. The startOver() function resets the questionNumber to 0, and calls a nextQuestion() function, as well as printing a line for me to know if the function has been called, which would mean that the tap is registered, but the startOver() function is incorrect, but it won't print the line, which indicates that the function isn't being called at all.
Full ViewController:
import UIKit
class ViewController: UIViewController {
//Place your instance variables here
let allQuestions = QuestionBank()
var pickedAnswer : Bool = false
var questionNumber : Int = 0
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet var progressBar: UIView!
#IBOutlet weak var progressLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let firstQuestion = allQuestions.list[0]
questionLabel.text = firstQuestion.questionText
}
#IBAction func answerPressed(_ sender: AnyObject) {
if sender.tag == 1 {
pickedAnswer = true
}
else if sender.tag == 2 {
pickedAnswer = false
}
checkAnswer()
nextQuestion()
}
func updateUI() {
}
func nextQuestion() {
questionNumber += 1
if questionNumber <= allQuestions.list.count - 1 {
var nextQuestion = allQuestions.list[questionNumber]
questionLabel.text = nextQuestion.questionText
}
else {
let alert = UIAlertController(title: "Quiz Over", message: "You've Finished the Quiz, Press Restart to Start Over", preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Restart", style: .default) { action in self.startOver()
}
alert.addAction(restartAction)
self.present(alert, animated: false, completion: nil)
}
}
func checkAnswer() {
if pickedAnswer == allQuestions.list[questionNumber].answer{
print("good")
}
else if pickedAnswer != allQuestions.list[questionNumber].answer{
print("bad")
}
}
func startOver() {
questionNumber = -1
nextQuestion()
print(questionNumber)
print("Restarting")
}
}
I can't reproduce any issue based on the code you've shown, as you can see:
... so I think this is may be a problem with your Xcode setup. Possible issue:
You are building to the iPhone XR simulator which is slow.
You may have an old computer.
And you seem to have turned on Slow Animations in the Simulator app by mistake.
At the least try turning off Slow Animations and build for the 5s simulator. This will make the Simulator a lot more responsive. But on old machinery there may still be issues.
Try to present in main thread:
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}

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
}

How to perform a segue only if a condition is true

I have two view controllers, the first contains a button and when I press that button I want it to check the textField I have to see if It contains any value and if it doesn't. I want a alert to appear saying to "input a value". If it does contain a value I would like to transfer to the next view controller.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "segue1"{
if txtField.text?.isEmpty == true{
//alert "input value"
return false;
}
else{
}
//segue occurs no problem
return true;
}
I've tried many other ways to do this and none seem to work. Any information is appreciated, Thank you. (New to swift and Stack Overflow Sorry)
Your reasoning is correct. Check the following points to make sure your code is working:
You have a segue from your button to your destination view controller.
Your segue identifier is segue1 as in your code.
Your isEmpty works when the UITextField is empty.
If those are correct you need to override shouldPerformSegue(withIdentifier:sender:) as you did but you can improve the code and add the alert by doing the following:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "segue1" {
if txtField.text?.isEmpty {
let alertController = UIAlertController(
title: "Alert",
message: "Input value",
preferredStyle: .alert
)
present(alertController, animated: true, completion: nil)
return false
}
}
return true
}
For more information about how to use UIAlertController check the Documentation

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 to pass value from DetailView to TableViewList [duplicate]

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

Resources