Problem with empty value of variable in SWIFT - swift

I'm making simple app- single view app with segues. I've create a model with few variables:
import Foundation
class ActualValues {
var id: String = "ID:"
var product: String?
var issue: String?
In deferent view controller I can change and save value of this (for example id):
class LoginViewController: UIViewController {
var workModel: ActualValues?
var closureBlock: (() -> Void)?
#IBOutlet weak var idTextField: UITextField!
#IBOutlet weak var idCheckButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func idCheckButtonDidPress(_ sender: UIButton) {
workModel?.id = "ID: \(idTextField.text!)"
closureBlock?()
self.dismiss(animated: true, completion: nil)
}
func configureID(model: ActualValues) {
workModel = model
}
}
And now, I want to take my variable "id" value and send it to the server. If I do that, the value of id is empty. And this is my problem. I created func in other model:
func startSending() {
db.collection("\(currentMonth())").document("Damian").collection("Michal").addDocument(data: [
"ID": "\(String(describing: av.id))",
"Start": "\(currentDate())",
"Product": "\(String(describing: av.product))"
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
Anyone can help me? Problem is because I can't send id value to the server- id is empty.
PS: I've used closure becouse I had a problem with display actual id in label on my HomeViewController.

You should create an instance of ActualValues for workModel first. Now your workModel is nil and you can't add an id to your property (workModel). You can set a breakpoint to idCheckButtonDidPress and print workModel to see if it is nil.
Your ActualValues class must be like ->
class ActualValues {
var id: String
var product: String?
var issue: String?
init(id: String, product: String?, issue: String?) {
self.id = id
self.product = product
self.issue = issue
}
}
And then you should call configureID with new model ->
#IBAction func idCheckButtonDidPress(_ sender: UIButton) {
let model = ActualValues(id: "ID: \(idTextField.text!)", product: nil, issue: nil)
configureID(model: model)
closureBlock?()
dismiss(animated: true)
}
Now you have an instance of ActualValues and we added id into it. You can use workModel to send the object.

Related

Boolean returns nil and unable to access value from response on view controller

I have a usermodel that checks the backend if the email exists - then I drill back into a viewcontroller and set a boolean value that should trigger a function run. However the value is unchanged and I am trying to change this value from the usermodel but it is not accessible. I understand why it does not work.. but do not know how to resolve the issue.
static func sendEmailWithResetLink(email: String) {
let params : Parameters = [
PARAM_EMAIL : email
]
request(URL_RESET_PASSWORD as String, method: .post, parameters: params, headers: nil).responseJSON {
(response: DataResponse<Any>) in
hideProgress()
print("this is response \(response)")
switch(response.result)
{
case .success(_):
print("it did not fail")
let passwordResetVC = PasswordResetViewController()
passwordResetVC.hasFailed = false
break
case .failure(_):
print("it failed")
let passwordResetVC = PasswordResetViewController()
//here boolean is set that I am trying to access in viewcontroller
passwordResetVC.hasFailed = true
break
}
}
}
Here's what I would suggest. You probably have some of these in place already:
Create an PasswordResetViewController object has an #IBAction func resetButtonClicked triggered by a button or whatever, which kicks off the password reset process.
Create a UserManager class. This class is responsible for all profile management activies in your app. Among other things, it has the ability to reset user passwords. This UserManager would probably be a singleton, that' sprobably good enough for now.
Create a new UserManagerDelegate protocol. Add to it all capabilities that are required by the UserManager to inform them of whatever happened. For example: var passwordResetHasFailed: Bool { get set }.
Extend your PasswordResetViewController conform to this protocol.
Your VC gets a reference to the singleton UserManager object, stores it in an instance variable, and uses that to access the shared object from then on.
Make your PasswordResetViewController register itself as the delegate to the user manager, with userManager.delegate = self
The #IBAction func resetButtonClicked will just call userManager.resetPassword()
Your UserManager does whatever it needs to do to reset the user's password.
When it's done, it'll call self.delegate?.passwordResetHasFailed = true/false.
Since your PasswordResetViewController registered itself as the delegate of the UserManager, when the operation is done, its passwordResetHasFailed property will be changed, giving it a chance to respond (by updating some UI or whatever).
There are some limitations to this approach, but it's a decent way to get started. Some thing to note:
This lets you unit test your PasswordResetViewController. You can create a MockUserManager, and set tesPasswordResetViewController.userManager = MockUserManager(), allowing you to separate out the user manager, and test PasswordResetViewController in isolation.
You'll run into issues if you need multiple objects to subscribe to receive delegate call backs (since there can only be 1 delegate object). At that point, you can switch to using something like Promises, RxSwift or Combine. But that's a problem for a later time, and the migration would be easy.
Going off of #Alexander - Reinstate Monica and what I assume what the code to look like to approach your problem.
Using MVC:
In Models folder (data/ logic part)
public class User {
private var name: String!
private var userEmail: String!
public var hasFailed: Bool?
init() {
name = ""
userEmail = ""
hasFailed = nil
}
public func setName(name: String) { self.name = name }
public func getName() -> String { return name }
public func setEmail(email: String) { userEmail = email }
public func getEmail() ->String { return userEmail }
public static func sendEmailWithRestLing(email: String) {
// your other code
switch response.result {
case .success(_):
//your code
hasFailed = false
break
case .failuare(_):
// your code
hasFailed = true
break
}
}
}
User Manager class applying singleton design
final class UserManager {
private var user = User()
static let instance = UserManager()
private init(){}
public func userName(name: String) {
if (name.count > 3) {
user.setName(name: name)
}
else { print("user name is too short") }
}
public func userEmail(email: String) {
if (email.count > 3) {
user.setEmail(email: email)
}
else { print("user email is too short") }
}
public func getUserName() -> String {
let name = user.getName()
if (name.isEmpty) { return "user name is Empty" }
return name
}
public func getUserEmail() -> String {
let email = user.getEmail()
if (email.isEmpty) { return "user email is Empty" }
return email
}
public func doKatieTask(link: String) -> Int {
guard let myValue = user.hasFailed else {
return -1
}
if (myValue) { return 1}
return 0
}
}
So, Now in the Controllers folder and since we a one-to-one relation we will use delegate design pattern. If had had one-to-many with the view controller. Use observers.
class ViewController: UIViewController {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var emailTextField: UITextField!
var _hasFail: Bool!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func doTask() {
UserManager.instance.userName(name: nameTextField.text!)
UserManager.instance.userEmail(email: emailTextField.text!)
switch UserManager.instance.doKatieTask(link: emailTextField.text!) {
case 0:
_hasFail = false
break
case 1:
_hasFail = true
break
default:
print("hasFailed is nil")
break
}
if let vc = storyboard?.instantiateViewController(identifier: "passwordVC") as? PasswordResetViewController {
vc.modalPresentationStyle = .fullScreen
vc.delegate = self
self.present(vc, animated: true, completion: nil)
}
}
}
extension ViewController: KatieDelegate {
var hasFailed: Bool {
get {
return _hasFail
}
set {
_hasFail = newValue
}
}
}
In PasswordReset UIViewController
protocol KatieDelegate {
var hasFailed: Bool { get set }
}
class PasswordResetViewController: UIViewController {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var emailLabel: UILabel!
var delegate: KatieDelegate?
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = UserManger.instance.getUserName()
emailLabel.text = UserManger.instance.getUserEmail()
if let delegate = delegate {
print("The value for has failed is: .....\(delegate.hasFailed)!")
}
else { print("error with delegate") }
}
}

How can I get a variable declared in a class in another class?

I just started to use Swift/firebase, sorry for my noobiness. I am writing data into firebase database using childbyautoid. I want to access the last key/id in another class.
let postName = Database.database().reference().child("Event").childByAutoId()
let postNameObject = [
"EventName": NameTextField.text,
"timestamp": [".sv":"timestamp"],
"userID": Auth.auth().currentUser!.uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
}
})
let childautoID = postName.key
I want to be able to call childautoID in another class our find another way to update this node.
You could maybe make a singleton class for managing Firebase methods. I do something similar whenever building apps with Firebase. This lets you reference values and use methods related to Firebase globally, across multiple classes. Before I adopted this method, I found myself rewriting the same code for uploading objects to firebase. A singleton will let you create reusable code, including storage of a "last key" set in this global class.
class FirebaseManager {
//You've probably seen something similar to this "shared" in other Apple frameworks
//Maybe URLSession.shared or FileManager.default or UserDefaults.standard or SKPaymentQueue.default() or UIApplication.shared
static var shared = FirebaseManager()
func createEvent(name: String, timeStamp: [String:String], uid: String) {
let postNameObject = [
"EventName": name,
"timestamp": timeStamp,
"userID": uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
//Do something
}
})
let childautoID = postName.key
//Whatever else you need to do in the function below here...
}
}
Usage:
class SomeViewController: UIViewController {
#IBOutlet var nameTextField: UITextField!
//Some code...
#IBAction func createEventButtonPressed(_ sender: Any) {
FirebaseManager.shared.createEvent(name: nameTextField.text, timeStamp: [".sv":"timestamp"], uid: Auth.auth().currentUser!.uid)
}
//Some more code...
}
Similarly, you could add a value called lastKey of type String in our FirebaseManager class. Note the variable we add to the top of our FirebaseManager class:
class FirebaseManager {
var lastKey: String!
static var shared = FirebaseManager()
func createEvent(name: String, timeStamp: [String:String], uid: String) {
let postNameObject = [
"EventName": name,
"timestamp": timeStamp,
"userID": uid
] as [String:Any]
postName.setValue(postNameObject, withCompletionBlock: { error, ref in
if error == nil {
self.dismiss(animated: true, completion: nil)
} else {
//Do something
}
})
let childautoID = postName.key
//Whatever else you need to do in the function below here...
}
}
Similarly, we can set this value in one view controller:
class ViewControllerA: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
FirebaseManager.shared.lastKey = "aBcDeFg9876543210"
}
}
And grab this value in another view controller that loads following the previous view controller:
class ViewControllerB: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(FirebaseManager.shared.lastKey)
}
}
Prints: aBcDeFg9876543210
This is the beauty of the static keyword. You can learn more about creating singleton classes here: https://cocoacasts.com/what-is-a-singleton-and-how-to-create-one-in-swift

Automatically delete data from Firebase Database

I have seen some other questions asked but I am having trouble getting it to work. I have a Mac app coded in swift and it has a Firebase login but the user types a key in that is stored on Firebase, is there a way to automatically delete that key when the user has successfully used it?
This is my database.
This is the code that is used currently.
import Cocoa
import FirebaseAuth
import FirebaseDatabase
class LoginViewController: NSViewController {
#IBOutlet weak var textUsername: NSTextField!
#IBOutlet weak var textPassword: NSSecureTextFieldCell!
#IBOutlet weak var btnLogin: NSButton!
var keyArray = \[Int64\]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
}
func getLoginState() -> Bool{
let state = UserDefaults.standard.bool(forKey: "isRegistered")
if (state) {
return true
} else {
return false
}
}
override func viewDidAppear() {
let state = self.getLoginState()
if (state){
self.performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "loginsegue"), sender: nil)
self.view.window?.close()
}
var ref: DatabaseReference!
ref = Database.database().reference()
let keyRef = ref.child("key1")
keyRef.observe(DataEventType.childAdded, with: { (snapshot) in
// let postDict = snapshot.value as? \[String : AnyObject\] ?? \[:\]
let keyStr = snapshot.value as? Int64
if let actualPost = keyStr{
self.keyArray.append(actualPost)
}
})
}
#IBAction override func dismissViewController(_ viewController: NSViewController) {
dismiss(self)
}
#IBAction func close(sender: AnyObject) {
self.view.window?.close()
}
#IBAction func onSignup(_ sender: Any) {
// self.performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "gotosignup"), sender: sender)
// self.view.window?.close()
}
func dialogOK(question: String, text: String) -> Void {
let alert: NSAlert = NSAlert()
alert.messageText = question
alert.informativeText = text
alert.alertStyle = NSAlert.Style.warning
alert.addButton(withTitle: "OK")
alert.runModal()
}
#IBAction func onLogin(_ sender: Any) {
//self.btnLogin.isEnabled = false
var isKey = false
if (!self.textUsername.stringValue.isEmpty) {
for key in keyArray{
if(Int64(self.textUsername.stringValue)! == key)
{
UserDefaults.standard.set(true, forKey:"isRegistered")
self.performSegue(withIdentifier: NSStoryboardSegue.Identifier(rawValue: "loginsegue"), sender: nil)
self.view.window?.close()
isKey = true
}
}
if (!isKey){
self.dialogOK(question: "Error", text: "Invalid Key")
}
} else {
self.dialogOK(question: "Error", text: "Please Input Key")
}
}
}
You can't sort your database like that and expect a working code, even if there's any. It will make a messy code:
You need to:
Sort your database like [1220:0]. the key first. 0 & 1 as an indicator if it's used or not.
Once the user taps onLogin() you need to set the used key value to 1
Setup Cloud Functions to check if the used key is equal to 1, if yes. then remove the key.
Do the rest of the work.
Related Articles to get you started:
Extend Realtime Database with Cloud Functions
functions.database.RefBuilder

Swift MVC architecture with model observer

Using the MVC approach for iOS app development, I would like to observe changes to the model by posting to the NotificationCenter. For my example, the Person.swift model is:
class Person {
static let nameDidChange = Notification.Name("nameDidChange")
var name: String {
didSet {
NotificationCenter.default.post(name: Person.nameDidChange, object: self)
}
}
var age: Int
var gender: String
init(name: String, age: Int, gender: String) {
self.name = name
self.age = age
self.gender = gender
}
}
The view controller that observes the model is shown below:
class ViewController: UIViewController {
let person = Person(name: "Homer", age: 44, gender: "male")
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var ageLabel: UILabel!
#IBOutlet weak var genderLabel: UILabel!
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var ageField: UITextField!
#IBOutlet weak var genderField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
nameLabel.text = person.name
ageLabel.text = String(person.age)
genderLabel.text = person.gender
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateLabels),
name: Person.nameDidChange, object: nil)
}
#IBAction func updatePerson(_ sender: Any) {
guard let name = nameField.text, let age = ageField.text, let gender = genderField.text else { return }
guard let ageNumber = Int(age) else { return }
person.name = name
person.age = ageNumber
person.gender = gender
}
#objc func updateLabels() {
nameLabel.text = person.name
ageLabel.text = String(person.age)
genderLabel.text = person.gender
}
}
The example app works as follows:
Enter a name, age, and gender in the text fields
Press the updatePerson button to update the model from the text field values
When the model is updated, the notification observer calls the updateLabels function to update the user interface.
This approach requires the person.name to be set last otherwise the updatePerson button must be pressed twice to update the entire user interface. And since I'm only observing one property, the notification does not represent the entire class. Is there a better way to observe changes of models (a class or struct) in Swift?
Note - I am not interested in using RxSwift for this example.
This is more of a dumping comment than a fulfilling answer. But long story short KVO is the feature you should be using, not NotificationCenter. The binding process becomes significantly more simple in Swift4
As for what KVO is: See here and here. For some examples which are MVVM focused you can see here and here. And don't let the MVVM sway you away. It's just MVC with bindings which you are trying to do the exact same thing + moving the presentation logic to a different layer.
A simple KVO example in Swift 4 would look like this:
#objcMembers class Foo: NSObject {
dynamic var string: String
override init() {
string = "hotdog"
super.init()
}
}
let foo = Foo()
// Here it is, kvo in 2 lines of code!
let observation = foo.observe(\.string) { (foo, change) in
print("new foo.string: \(foo.string)")
}
foo.string = "not hotdog"
// new foo.string: not hotdog
You can also create your own Observable type like below:
class Observable<ObservedType>{
private var _value: ObservedType?
init(value: ObservedType) {
_value = value
}
var valueChanged : ((ObservedType?) -> ())?
public var value: ObservedType? {
get{
return _value // The trick is that the public value is reading from the private value...
}
set{
_value = newValue
valueChanged?(_value)
}
}
func bindingChanged(to newValue : ObservedType){
_value = newValue
print("value is now \(newValue)")
}
}
Then to create an observable property you'd do:
class User {
// var name : String <-- you normally do this, but not when you're creating as such
var name : Observable<String>
init (name: Observable<String>){
self.name = name
}
}
The class above (Observable) is copied and pasted from Swift Designs patterns book
To simply visualize the picture, you should be aware of the fact that you are observing only the name change. So it doesn't make sense to update all of the other properties of Person. You are observing name change and it's being updated accordingly, let alone others.
So it's not an ideal assumption that age and gender might have been changed in the process of changing name. Being said that, you should consider observing all of the properties one by one and bind actions differently and modify only the UI component that is mapped to that specific property.
Something like this:
override func viewDidLoad() {
...
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateName),
name: Person.nameDidChange, object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateAge),
name: Person.ageDidChange, object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(self.updateGender),
name: Person.genderDidChange, object: nil)
...
}
#objc func updateName() {
nameLabel.text = person.name
}
#objc func updateAge() {
ageLabel.text = String(person.age)
}
#objc func updateGender() {
genderLabel.text = person.gender
}

Value of type '[Users]' has no member 'username'

I'm trying to create a simple user login ViewController that connects to a database full of user information for my app but I get the error stated in the title.
import UIKit
import Alamofire
class Users: Decodable {
let username: String
let email: String
let password: String
init(username: String, email: String, password: String) {
self.username = username
self.email = email
self.password = password
}
class LoginVC: UIViewController {
var loggingin = [Users]()
#IBOutlet weak var usernameTxtField: UITextField!
#IBOutlet weak var passwordTxtField: UITextField!
#IBOutlet weak var checkCredsBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let jsonURL = "http://host-2:8888/getLogin.php"
let url = URL(string: jsonURL)
URLSession.shared.dataTask(with: url!) { (data, response, error) in
do {
self.loggingin = try JSONDecoder().decode([Users].self, from: data!)
for eachUser in self.loggingin {
print(eachUser.username + " : " + eachUser.password)
}
}
catch {
print("Error")
}
}.resume()
}
#IBAction func checkCreds(_ sender: Any) {
if usernameTxtField == loggingin.username && passwordTxtField == loggingin.password {
print("YES")
}
}
}
}
The reason of this compile time error appears in checkCreds function, you are trying to access the username and password properties directly from the array which is obviously incorrect. What you should do instead is to get the desired object from loggingin array and do comparing with its properties:
#IBAction func checkCreds(_ sender: Any) {
let currentUser = loggingin[0]
if usernameTxtField == currentUser.username && passwordTxtField == currentUser.password {
print("YES")
}
}
In the above example, I just got the first object; I would assume that you already able to get the desired object currentUser (what's the used index) for getting the object from the loggingin.