2

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

Community
  • 1
  • 1
Camillo
  • 544
  • 1
  • 6
  • 24
  • 1
    What is `OnboardingRegistrationFormController`? An instance of `LoginViewController` or just a call to a class method? – Nicolas B. Jan 29 '15 at 21:08
  • Fixed, I meant `LoginViewController` (eventually I'll populate one emailField in both `LoginViewController` and `OnboardingRegistrationFormController `) – Camillo Jan 29 '15 at 21:10

3 Answers3

2

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

}

Jeremy Pope
  • 3,342
  • 1
  • 16
  • 17
  • Thanks a lot for your answer. I tried calling updateLatestEmailAddressFromUserTable() from viewDidAppear() instead of viewDidLoad (I thought it would allow some time for sqlite to grab the data) - turns out it doesn't work either. – Camillo Jan 29 '15 at 21:32
  • Hi Jeremy, thank you for your suggestion. Your code seems to have an issue: http://i.imgur.com/jwCeg5O.png See my related question here: http://stackoverflow.com/questions/28231913/add-blocking-to-swift-for-loop – Camillo Jan 30 '15 at 08:35
  • Sorry, yes because the return is in a for loop which technically might not run. If you are limiting it to 1, you likely don't need to iterate through a loop, but rather:`let user = users.limit(1).order(time.desc)` `return user[0][email]` – Jeremy Pope Jan 30 '15 at 15:44
2

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
    }

}
Gerwin
  • 1,572
  • 5
  • 23
  • 51
Shashi3456643
  • 2,021
  • 17
  • 21
  • Thanks a lot for your suggestions. I am really new to swift and it would really help me a lot if you could give me a link to a related code snippet where I can learn how to implement protocol, delegate, update method etc. – Camillo Jan 29 '15 at 21:31
  • You are welcome Luca. There is one more approach to accomplish this is by using block. – Shashi3456643 Jan 29 '15 at 23:25
0

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

Nicolas B.
  • 1,318
  • 1
  • 11
  • 20
  • I've read a lot about _delegates_... would those help in my case? Moreover, how could I implement this (either with my current viewcontroller structure or with another structure)? Thanks a lot for your help btw! – Camillo Jan 29 '15 at 21:16
  • A proper way to implement this would be to ask the data and update your textField from inside the `LoginViewController`. It's called MVC (for Model-View-Controller) and it basically says that Views (labels, textFields...) and Models (raw data) should be allowed to talk to each-other. Only the Controller is allowed to 'fetch' data, and update Views accordingly. – Nicolas B. Jan 29 '15 at 21:20
  • In iOS, delegates are a way for objects (Views, and Controllers mostly) to notify other objects that something interesting happened to them. For example: Something was selected, or a tab was tapped... – Nicolas B. Jan 29 '15 at 21:21