How to access variable from NSObject class? - ios

I'm trying to learn mvc design pattern in swift. So I made the model class named User like below :
class User: NSObject {
var email : String!
var password : String!
var profilePictureUrl : String!
init(email: String, password: String, profilePictureUrl: String) {
super.init()
self.email = email
self.password = password
self.profilePictureUrl = profilePictureUrl
}}
and I'm using another class that store the function named loginConnection:
class loginConnection: NSObject {
class func loginUserWithEmailPassword(email: String,password: String) -> User{
return User(email: email, password: password, profilePictureUrl: "nil")
}}
And I try to set and get the email,password, and profilePictureUrl from my loginViewController but I always get nil when I print the User object.
var userObj : User!
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty != nil && userPasswordTextField.text?.isEmpty != nil{
loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
}
}
#IBAction func registerAction(sender: UIButton) {
print("\(userObj.email) >>>>> \(userObj.password)")
}
How can I access variable from User class?

Change your loginAction method as below,
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty == false && userPasswordTextField.text?.isEmpty == false {
self.userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
print("\(userObj.email) >>>>> \(userObj.password)")
}
}
1) you were comparing userEmailTextField.text?.isEmpty with nil, isEmpty returns Bool value.
2) you were not assigning value of type User returned by the function loginUserWithEmailPassword.

so then you have to do the following:-
var userObj : User = User()
userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
after that
userObj.email

Are you calling userObj from loginAction?
Like Below..
var userObj : User!
#IBAction func loginAction(sender: UIButton) {
if userEmailTextField.text?.isEmpty != nil && userPasswordTextField.text?.isEmpty != nil{
userObj = loginConnection.loginUserWithEmailPassword(userEmailTextField.text!, password:userPasswordTextField.text!)
print("\(userObj.email) >>>>> \(userObj.password)")
}
}
loginUserWithEmailPassword return User class object so you can use that to access User class properties

I can't see why this needs to be an NSObject. By making it a struct you can remove the init since it will come automatically. Also remove the ! in most cases since it quite dangerous to use implicitly unwrapped optionals unless you know what you're doing. Xcode will also help with the autocompletion and give good suggestions on how to fix as you go. If you do this you'll find the compiler tell you about the problems before run time errors can happen.

You declared userObj instance of User but you didn't assigned it by the values will return from loginUserWithEmailPassword function.
Assign you userObj as below in your viewController loginAction.
#IBAction func loginAction(sender: UIButton) {
userObj = loginConnection.loginUserWithEmailPassword("Karan", password:"karanPassword")
self.registerAction()
}
Now you will get the assigned username and password.
#IBAction func registerAction(sender: UIButton) {
print("\(userObj.email) >>>>> \(userObj.password)")
}
Here I got the username and password on my output window

Related

Xcode 9.4.1: Value of type 'String' has no member 'setValue'

I'm learning Swift from a Udemy tutorial that shows how to make a chat app using a Firebase database. For my own learning and as a quick reference guide, I typed the code in a single .swift file to get a quick overview of the entire app and practice debugging. However, I have one more compiler error saying that the constant 'messagesDB' doesn't have a member 'setValue'. I'm assuming that messagesDB being an instance of class 'Database' would have access to the setValue() instance method. What do you think I'm missing in order to silence this error? Does it have something to do with the way the functions are declared?
Both class Auth and Database are arbitrary classes to mimic Firebase, so that the rest of the code could be displayed without a bunch of errors, thus giving me a single file to see how things work.
class Auth {
var currentUser: String = ""
func auth() -> Self { return self }
}
class Database {
func setValue() -> Self { return self }
func database() -> Self { return self }
func reference() -> Self { return self }
func child(_ someString: String) -> String {
print(someString)
}
}
class ChatViewController: UIViewController {
#IBOutlet var messageTextfield: UITextField!
#IBOutlet var sendButton: UIButton!
#IBAction func sendPressed(_ sender: AnyObject) {
messageTextfield.endEditing(true)
messageTextfield.isEnabled = false
sendButton.isEnabled = false
let messagesDB = Database().database().reference().child("Messages")
let messageDictionary = ["Sender": Auth().auth().currentUser, "MessageBody": messageTextfield.text!] as [String : Any]
messagesDB.setValue(messageDictionary) { //ERROR: Value of type 'String' has no member 'setValue'
(error, reference) in
if error != nil {
print(error!)
} else {
print("Message saved successfully!")
self.messageTextfield.isEnabled = true
self.sendButton.isEnabled = true
self.messageTextfield.text = ""
}
}
}
}
It's easy, your messageDB constant it's being set equals to the function "child" that returns a string, that's why it's telling you that "string" doesn't has no member setValue, so you should just set your messageDB to Database().database().reference()
the child method returns a String and not a Database instance, hence the error message.
create the database instance first and then use it where needed:
let messagesDB = Database()
.
.
messagesDB.setValue(...

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)
}
}

Sync data between two viewcontrollers to avoid creating same observer again

In my main VC I look for changes in my FB database like this:
ref.child("posts").observe(.childChanged, with: { (snapshot) in
.......
})
From this VC I can enter VC2 which is set to be "present modally" in my segue.
Now I wonder if I can pass live FB data from VC1 to VC2? I know that I can use a segue.identifier and pass data when I segue to the next VC but this is one time send only. Or should I setup a delegate to fetch data from vc1 to vc2?
So is there any way I can send data from VC1 to VC2 once a node has been updated or must I setup a new .observe() function in VC2?
First I would like to remind you about the singleton design patter :
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
So the first thing you need to do is to create a call that contains as a parameters the data you get from firebase, I have did the following to get the following in order to get the user data when he logged in into my app and then use these data in every part of my application (I don't have any intention to pass the data between VC this is absolutely the wrong approach )
my user class is like this :
import Foundation
class User {
static let sharedInstance = User()
var uid: String!
var username: String!
var firstname: String!
var lastname: String!
var profilePictureData: Data!
var email: String!
}
after that I have created another class FirebaseUserManager (you can do this in your view controller but it's always an appreciated idea to separate your view your controller and your model in order to make any future update easy for you or for other developer )
So my firebaseUserManager class contains something like this
import Foundation
import Firebase
import FirebaseStorage
protocol FirebaseSignInUserManagerDelegate: class {
func signInSuccessForUser(_ user: FIRUser)
func signInUserFailedWithError(_ description: String)
}
class FirebaseUserManager {
weak var firebaseSignInUserManagerDelegate: FirebaseSignInUserManagerDelegate!
func signInWith(_ mail: String, password: String) {
FIRAuth.auth()?.signIn(withEmail: mail, password: password) { (user, error) in
if let error = error {
self.firebaseSignInUserManagerDelegate.signInUserFailedWithError(error.localizedDescription)
return
}
self.fechProfileInformation(user!)
}
}
func fechProfileInformation(_ user: FIRUser) {
var ref: FIRDatabaseReference!
ref = FIRDatabase.database().reference()
let currentUid = user.uid
ref.child("users").queryOrderedByKey().queryEqual(toValue: currentUid).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
let dict = snapshot.value! as! NSDictionary
let currentUserData = dict[currentUid] as! NSDictionary
let singletonUser = User.sharedInstance
singletonUser.uid = currentUid
singletonUser.email = currentUserData["email"] as! String
singletonUser.firstname = currentUserData["firstname"] as! String
singletonUser.lastname = currentUserData["lastname"] as! String
singletonUser.username = currentUserData["username"] as! String
let storage = FIRStorage.storage()
let storageref = storage.reference(forURL: "gs://versus-a107c.appspot.com")
let imageref = storageref.child("images")
let userid : String = (user.uid)
let spaceref = imageref.child("\(userid).jpg")
spaceref.data(withMaxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
// Uh-oh, an error occurred!
print(error.localizedDescription)
} else {
singletonUser.profilePictureData = data!
print(user)
self.firebaseSignInUserManagerDelegate.signInSuccessForUser(user)
}
}
}
})
}
}
so basically this class contains some protocols that we would implements and two functions that manager the firebase signIn and fechProfileInformation , that will get the user information
than in my login View controller I did the following :
1 implement the protocol
class LoginViewController: UIViewController, FirebaseSignInUserManagerDelegate
2 in the login button I did the following
#IBAction func loginAction(_ sender: UIButton) {
guard let email = emailTextField.text, let password = passwordTextField.text else { return }
let firebaseUserManager = FirebaseUserManager()
firebaseUserManager.firebaseSignInUserManagerDelegate = self
firebaseUserManager.signInWith(email, password: password)
}
3 implement the protocol method :
func signInSuccessForUser(_ user: FIRUser) {
// Do something example navigate to the Main Menu
}
func signInUserFailedWithError(_ description: String) {
// Do something : example alert the user
}
So right now when the user click on the sign in button there is an object created which contains the user data save on firebase database
now comes the funny part (the answer of your question : how to get the user data in every where in the app)
in every part of my app I could make
print(User.sharedInstance.uid) or print(User.sharedInstance. username)
and I get the value that I want to.
PS : In order to use the singleton appropriately you need to make sure that you call an object when it's instantiated.

func == from equatable protocol does not work for custom object, swift

My goal is to show a user list of history logins ( such as username ) if there are any. In order to do that, I am doing
1. Create an custom object named User like below
class User: NSObject
{
var login: String
init(login: String)
{
self.login = login
}
required init(coder aDecoder: NSCoder) {
login = aDecoder.decodeObjectForKey("login") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(login, forKey: "login")
}
}
// This conform to make sure that I compare the `login` of 2 Users
func ==(lhs: User, rhs: User) -> Bool
{
return lhs.login == rhs.login
}
At UserManager, Im doing save and retrieve an User. Before saving, I'm doing a check if the the list of history logins contains a User, I wont add it in, otherwise.
class UserManager : NSObject
{
static let sharedInstance = UserManager()
var userDefaults = NSUserDefaults.standardUserDefaults()
func saveUser(user:User)
{
var users = retrieveAllUsers()
// Check before adding
if !(users.contains(user))
{
users.append(user)
}
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(users)
userDefaults.setObject(encodedData, forKey: "users")
userDefaults.synchronize()
}
func retrieveAllUsers() -> [User]
{
guard let data = userDefaults.objectForKey("users") as? NSData else
{
return [User]()
}
let users = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [User]
// Testing purpose
for user in users
{
print(user.login)
}
return users
}
}
At first time trying, I do
UserManager.sharedInstance.saveUser(User(login: "1234"))
Now it saves the first login. At second time, I also do
UserManager.sharedInstance.saveUser(User(login: "1234"))
UserManager still adds the second login into nsuserdefault. That means the function contains fails and it leads to
func ==(lhs: User, rhs: User) -> Bool
{
return lhs.login == rhs.login
}
does not work properly.
Does anyone know why or have any ideas about this.
The problem is that User derives from NSObject. This means that (as you rightly say) your == implementation is never being consulted. Swift's behavior is different for objects that derive from NSObject; it does things the Objective-C way. To implement equatability on an object that derives from NSObject, override isEqual:. That is what makes an NSObject-derived object equatable in a custom way, in both Objective-C and Swift.
Just paste this code right into your User class declaration, and contains will start working as you wish:
override func isEqual(object: AnyObject?) -> Bool {
if let other = object as? User {
if other.login == self.login {
return true
}
}
return false
}
What's going on?
As #matt already said, the problem is about equality.
Look
var users = [User]()
users.append(User(login: "1234"))
users.contains(User(login: "1234")) // false
Look again
var users = [User]()
let user = User(login: "1234")
users.append(user)
users.contains(user) // true <---- THIS HAS CHANGED
contains
The contains function is NOT using the logic you defined here
func ==(lhs: User, rhs: User) -> Bool {
return lhs.login == rhs.login
}
Infact it is simply comparing the memory addresses of the objects.
Solution
You can solve the issue passing your own logic to contains, just replace this
if !(users.contains(user)) {
users.append(user)
}
with this
if !(users.contains { $0.login == user.login }) {
users.append(user)
}

Swift: Set UITextField.text from class (retrieved value from SQLite)

I am using Swift with SQLite.swift. I have the following UIViewController:
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
func setEmailAddress(email:String){
emailField.text = email
}
override func viewDidLoad() {
MySQLite().updateLatestEmailAddressFromUserTable() // breaks here (email is in console, though...)
}
}
Then I am trying to update it's value (through the setEmailAddress function) from another class:
class MySQLite {
func updateLatestEmailAddressFromUserTable(){
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
LoginViewController().setEmailAddress(user[email]) // breaks here
}
}
}
above code gives me the following error
fatal error: unexpectedly found nil while unwrapping an Optional value
To explain a little further: I am retrieving the most recent entry in SQLite table to get the user's email address and update the text field in the login view controller. This allows for easier log in for returning users.
I have been struggling with this for over 2 hours now and trying various things. The main problem I believe is that when I try to simply return the email address as string from my second function and set the field directly from LoginViewController, it doesn't work (SQLite related code was not "executed" yet I believe).
possibly related thread (Obj-C):
set UITextField.text from another class
Here whats happening LoginViewController().setEmailAddress(user[email]) creates new instance of LoginViewController which is not same as your current LoginViewController.
Why don't you make protocol and define as delegate in MySQLite
And LoginViewController will have implementation of update method. Pass the delegate to MySqlite
In MySQLite when you get the value form database call the delegate update method.
Example
MySQLite
protocol loginDelegate
{
func update(NSString)
}
class MySQLite {
var delegate:loginDelegate?
func updateLatestEmailAddressFromUserTable(){
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
if((delegate) != nil)
{
delegate?.update("example#example.com")
}
}
}
}
class LoginViewController: UIViewController,loginDelegate {
#IBOutlet weak var emailField: UITextField!
func setEmailAddress(email:String){
emailField.text = email
}
override func viewDidLoad() {
var mySQLite: MySQLite=LoginClass();
mySQLite.delegate=self;
[mySQLite .updateLatestEmailAddressFromUserTable()];
}
func update(email: NSString) {
println(email);
emailField.text = email
}
}
Make sure that the view which has the emailField has been instantiated on the screen.
#IBOutlet weak var emailField: UITextField!
This is an optional, which will be nil until the storyboard or nib for it is loaded. I assume OnBoardingRegistrationFormController is an instance of your LoginViewController class?
I see you've accepted an answer, but in this case creating a protocol is likely overkill. If sqlite is your model, why not just have the function return a value, and then you can assign the value to the text field in the controller. ex.
class LoginViewController: UIViewController {
#IBOutlet weak var emailField: UITextField!
override func viewDidLoad() {
emailField.text = MySQLite().updateLatestEmailAddressFromUserTable()
}
}
class MySQLite {
func updateLatestEmailAddressFromUserTable() -> String{
let dbPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first as String
let db = Database("\(dbPath)/db.sqlite3")
let users = db["users"]
let id = Expression<Int>("id")
let email = Expression<String>("email")
let time = Expression<Int>("time")
for user in users.limit(1).order(time.desc) {
println(user[email]) // this works, correctly outputs in console: email#domain.com
return user[email]
}
}
}
The issue is that LoginViewController's view isn't loaded when you try to assign a text to the textField. i.e: emailField is nil and unwrapping nil values leads to a runtime crash (since the outlet has not been connected to it's storyboard/xib counterpart).

Resources