Rorschach Rorschach - 5 months ago 10
Swift Question

Executing a ViewModel function before setting variables in ViewController

I have an issue with code order in a ItemsViewController.swift

When I run my code it starts the for items loop before my api returns the values for the items. This is done in the line:

self.viewModel/getItemsTwo...
Therefore it thinks that items is nil by the time the loop starts, so it errors with:

fatal error: unexpectedly found nil while unwrapping an Optional value


How can I start the loop only after items has been filled by the api call/function call?

class ItemsViewController: UIViewController {
private let viewModel : ItemsViewModel = ItemsViewModel()

override func viewDidLoad() {
super.viewDidLoad()



self.viewModel.getItemsTwo(self.viewModel.getCurrentUser())
var items = self.viewModel.items

for item in items! {
print(item)
}
}
...


The getItemsTwo function in the viewModel sets the viewModel.items variable when it is called

EDIT 1

ItemsViewModel.swift

...
var items : JSON?
...

func getItemsTwo(user: MYUser) {

let user_id = user.getUserId()
let url = String(format:"users/%@/items", user_id)
self.get(url).responseJSON { (response) -> Void in
let dataExample = response.data
var newdata = JSON(data: dataExample!)
self.items = newdata
}
}
...


EDIT 2

I am trying to do this:

just change it in the ViewController to:

var items = self.viewModel.getItemsTwo(self.viewModel.getCurrentUser())


and the ViewModel to:

func getItemsTwo(user: MYUser) -> JSON {

let user_id = user.getUserId()
let url = String(format:"users/%@/items", user_id)
self.get(url).responseJSON { (response) -> Void in
let dataExample = response.data
var newdata = JSON(data: dataExample!)
self.items = newdata
}
return self.items
}


But the return statement still errors as if self.items in nil.

Answer

Maybe you could expand your getItemsTwo method to take a callback closure, something like:

func getItemsTwo(user: MYUser, callback: (items: [JSON])-> Void)

Meaning that you have a parameter called callback which is a closure function that returns Void and takes an array of JSON items as an input parameter.

Once you have added newdata to self.items you could call your callback closure like so:

func getItemsTwo(user: MYUser, callback: (items: [JSON])-> Void) {
    let user_id = user.getUserId()
    let url = String(format:"users/%@/items", user_id)
    self.get(url).responseJSON { (response) -> Void in
        let dataExample = response.data
        var newdata = JSON(data: dataExample!)
        self.items = new data

        //Items are now populated, call callback
        callback(items: self.items)
    }
}

And then, in your ItemsViewController you could say:

override func viewDidLoad() {
    super.viewDidLoad()
    self.viewModel.getItemsTwo(self.viewModel.getCurrentUser()) { items in
        for item in items {
            print(item)
        }
    }
}

Notice that if you add a closure as the last parameter you can use a so called "Trailing Closure" and place it "outside" or "after" your function as described in this chapter of "The Swift Programming Language".

Hope that helps you (I haven't checked in a compiler so you might get some errors, but then well look at them OK :)).