variable containing data(1) while printing under jsonserialization method but in viewdidload it's showing nil value(0)
var bank = Int()
func getProfile() {
.
.
.
.
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
print("json \(json)")
let status = json["Success"] as! Bool
if status {
//send profile update status 1
//After Alert Success
DispatchQueue.main.async {
if let banks = (json["bank"] as? NSString)?.intValue {
print("banks\(banks)")
self.bank = Int(banks)
print("self.bank\(self.bank)")
}
}
} else {
}
}
variable called under viewdidload showing 0(nil) value
override func viewDidLoad() {
getProfile()
print("bank \(bank)")
super.viewDidLoad()
// Do any additional setup after loading the view.
}
output: bank 0
The main issue the variable isn't set because the webservice is called in another thread and the variable is printed before it's even loaded by the web service.
Try to call it again in some button and you will see your result will print perfectly
Related
I have data that I want to read from disk into memory that takes a nontrivial amount of time.
I want to be able to do two things:
I don't the data to be read every time the view loads.
I want to be able to invoke it from another view.
lazy var data: [String: String] = {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}()
Above code gets initialized only once when the view loads for the first time, which is perfect for eliminating unnecessary computation. The problem is I also want to be able to trigger it from another view when needed.
I tried to trigger re-initialization:
func getData() {
guard let data = readFromDisk() else { return [:] }
data = processData(data: data)
}
and invoke it from another view:
let vc = ViewController()
vc.getData()
but, doesn't work.
I tried to see if I could use static since it's also lazy, but I get an error saying:
Instance member cannot be used on type 'ViewController'
Finally, I tried creating a separate class:
class DataImporter {
var data: [String: String] {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}
func readFromDisk() -> [String: String] {}
func processData(data: [String: String]) -> [String: String] {}
}
and have the lazy property in ViewController:
lazy var importer = DataImporter()
thinking that instantiating a class achieves the dual effect of taking advantage of a lazy property and invoking it when needed:
let vc = ViewController()
vc.importer = DataImporter()
This instantiates the class about a hundred times for some reason which is not ideal.
I would suggest creating a function that loads the data into data and then whenever you need to reload data, simply reassign it.
class DataStore {
lazy var data: [String: String] = loadData()
func readFromDisk() -> Data? {...}
func processData(data: Data) -> [String:String] { ... }
func loadData() -> [String:String] {
guard let data = readFromDisk() else { return [:] }
return processData(data: data)
}
}
let store = DataStore()
let data = store.data // only loaded here
store.data = store.loadData() // reloads the data
If you don't want the loadData function to be exposed, you can also create a separate reloadData function.
class DataStore {
...
func reloadData() {
data = loadData()
}
}
and then instead of doing store.data = store.loadData(), simply call store.reloadData()
I am trying to transfer a JSON value to a label in another view controller, from pressing a button in my Main View Controller. My first view controller is called 'ViewController' and my second one is called 'AdvancedViewController. The code below shows how I get the JSON data, and it works fine, displays the JSON values in labels in my MainViewController, but when I go to send a JSON value to a label in my AdvancedViewController, I press the button, it loads the AdvancedViewController but the label value is not changed? I have assigned the label in my AdvancedViewController and I'm not sure why its not working. I am trying to transfer it to the value 'avc.Label' which is in my advanced view controller
The main label code shows how I get it to work in my MainViewController
Code below:
My Main ViewController:
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
let weatherData = try decoder.decode(MyWeather.self, from: data)
if (self.MainLabel != nil)
{
if let gmain = (weatherData.weather?.first?.main) { //using .first because Weather is stored in an array
print(gmain)
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
}
}
}
let avc = AdvancedViewController(nibName: "AdvancedViewController", bundle: nil)
if (avc.Label != nil)
{
if let mTest = (weatherData.weather?.first?.myDescription) { //using .first because Weather is stored in an array
DispatchQueue.main.async {
avc.Label.text! = String (mTest)
}
}
}
In AdvancedViewController create variable that store the value of mTest
class AdvancedViewController: ViewController {
var test: String!
override func viewDidLoad(){
super.viewDidLoad()
if let test = test {
myLabel.text = test
}
}
}
let vc = storyboard?.instantiateViewController(withIdentifier: "AdvancedViewController") as! AdvancedViewController
if let mTest = (weatherData.weather?.first?.myDescription) {
vc.test = mTest
}
DispatchQueue.main.async {
present(vc, animated: true, completion: nil)
}
You shouldn't try to manipulate another view controller's views. That violates the OO principle of encapsulation. (And it sometimes just plain doesn't work, as in your case.)
Salman told you what to do to fix it. Add a String property to the other view controller, and then in that view controller's viewWillAppear, install the string value into the desired label (or do whatever is appropriate with the information.)
I am trying to get an array of temperatures in a given time period from an API in JSON format. I was able to retrieve the array through a completion handler but I can't save it to another variable outside the function call (one that uses completion handler). Here is my code. Please see the commented area.
class WeatherGetter {
func getWeather(_ zip: String, startdate: String, enddate: String, completion: #escaping (([[Double]]) -> Void)) {
// This is a pretty simple networking task, so the shared session will do.
let session = URLSession.shared
let string = "api address"
let url = URL(string: string)
var weatherRequestURL = URLRequest(url:url! as URL)
weatherRequestURL.httpMethod = "GET"
// The data task retrieves the data.
let dataTask = session.dataTask(with: weatherRequestURL) {
(data, response, error) -> Void in
if let error = error {
// Case 1: Error
// We got some kind of error while trying to get data from the server.
print("Error:\n\(error)")
}
else {
// Case 2: Success
// We got a response from the server!
do {
var temps = [Double]()
var winds = [Double]()
let weather = try JSON(data: data!)
let conditions1 = weather["data"]
let conditions2 = conditions1["weather"]
let count = conditions2.count
for i in 0...count-1 {
let conditions3 = conditions2[i]
let conditions4 = conditions3["hourly"]
let count2 = conditions4.count
for j in 0...count2-1 {
let conditions5 = conditions4[j]
let tempF = conditions5["tempF"].doubleValue
let windspeed = conditions5["windspeedKmph"].doubleValue
temps.append(tempF)
winds.append(windspeed)
}
}
completion([temps, winds])
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
// The data task is set up...launch it!
dataTask.resume()
}
}
I am calling this method from my view controller class. Here is the code.
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
}
//Here it prints out an empty array
print(weatherData)
}
The issue is that API takes some time to return the data, when the data is return the "Completion Listener" is called and it goes inside the "getWeather" method implementation, where it prints the data of array. But when your outside print method is called the API hasn't returned the data yet. So it shows empty array. If you will try to print the data form "weatherData" object after sometime it will work.
The best way I can suggest you is to update your UI with the data inside the "getWeather" method implementation like this:
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
// Update your UI here.
}
//Here it prints out an empty array
print(weatherData)
}
It isn't an error, when your controller get loaded the array is still empty because your getWeather is still doing its thing (meaning accessing the api, decode the json) when it finishes the callback will have data to return to your controller.
For example if you were using a tableView, you will have reloadData() to refresh the UI, after you assign data to weatherData
Or you could place a property Observer as you declaring your weatherData property.
var weatherData:[Double]? = nil {
didSet {
guard let data = weatherData else { return }
// now you could do soemthing with the data, to populate your UI
}
}
now after the data is assigned to wheaterData, didSet will be called.
Hope that helps, and also place your jsonParsing logic into a `struct :)
This is the class where the value is
class CurrentWeather{
var _date:String!
var _cityName:String!
var _temp:Double!
var _weatherType:String!
var cityName:String{
if _cityName==nil{
_cityName = ""
}
return _cityName
}
var currentTemprature:Double{
if _temp==nil{
_temp = 0.0
}
return self._temp
}
var weathertype:String{
if _weatherType==nil{
_weatherType = ""
}
return _weatherType
}
var date:String{
if _date==nil{
_date = ""
}
let dateFormater=DateFormatter()
dateFormater.dateStyle = .long
dateFormater.timeStyle = .none
let currentDate = dateFormater.string(from: Date())
self._date="\(currentDate)"
return _date
}
func weatherDataDownload(completed : downloadComplete){
let weatherUrl=URL(string: constant)!
Alamofire.request(weatherUrl , method:.get).responseJSON{response in
if let dict=response.result.value as? Dictionary<String,AnyObject>{
if let name=dict["name"] as? String{
self._cityName = name.capitalized
print(name.capitalized)
}
if let weather=dict["weather"] as? [Dictionary<String,AnyObject>]{
if let main=weather[0]["main"] as? String{
self._weatherType=main.capitalized
print(main.capitalized)
}
}
if let tempr=dict["main"] as? Dictionary<String,AnyObject>{
if let temp=tempr["temp"] as? Double{
let convertedTemp=Double(round(temp-273.15))
self._temp=convertedTemp
print(convertedTemp)
}
}
}
}
completed()
}}
This is the ViewController class
var currentWeatherOj = CurrentWeather()
override func viewDidLoad() {
super.viewDidLoad()
table.delegate=self
table.dataSource=self
currentWeatherOj.weatherDataDownload {
self.updateUIweather()
}
}
func updateUIweather () {
weatherType.text=currentWeatherOj.weathertype
presentDate.text=currentWeatherOj.date
presentLocation.text=currentWeatherOj.cityName
presentTemp.text="\(currentWeatherOj.currentTemprature)"
}
when I try to call in ViewController its showing the default value which I set inside of computed variable other than _date but I am able print values inside the func of weatherDataDownload.I am confused how variables in swift 3 works because of this.
See the comments in the following code sample. You need to move the call to "completed()"
func weatherDataDownload(#escaping completed : downloadComplete) {
let weatherUrl=URL(string: constant)!
Alamofire.request(weatherUrl , method:.get).responseJSON { response in
// ... leave your code here alone
// put the call to completed() here
completed()
}
// not here
}
When you make the all to Alamofire, it executes it's request on a background thread. When that request completes, it calls the closure that you've defined (the one that starts "response in..."). You don't want to call updateUIweather until that has been done so you put the call to "completed()" inside of the same completion handler.
When the call to completed was outside of that completion handler, it would be called right away... immediately after the Alamofire request was sent (but before it had finished on that background thread). None of the code in the completion handler has run yet so your variables aren't updated yet.
Finally because your completed closure was passed to a block that was then sent off to a background thread, that closure "escapes" the current function. You add the #escaping so that folks reading your code will know that the closure will live on beyond the life of that function.
I'm building an app with MVC Model.
I use lazy load technical to fill up a variable. (Model)
And this variable is being by one UIViewController (Controller)
But i don't know how to reload or trigger the view controller when the model action is finished. Here is my code
Model (lazy load data)
class func allQuotes() -> [IndexQuotes]
{
var quotes = [IndexQuotes]()
Alamofire.request(.GET, api_indexquotes).responseJSON { response in
if response.result.isSuccess && response.result.value != nil {
for i in (response.result.value as! [AnyObject]) {
let photo = IndexQuotes(dictionary: i as! NSDictionary)
quotes.append(photo)
}
}
}
return quotes
}
And the part of view controller
class Index:
UIViewController,UICollectionViewDelegate,UICollectionViewDataSource {
var quotes = IndexQuotes.allQuotes()
var collectionView:UICollectionView!
override func viewDidLoad() {
This is really serious question, i'm confusing what technic will be used to full fill my purpose?
Since Alamofire works asynchronously you need a completion block to return the data after being received
class func allQuotes(completion: ([IndexQuotes]) -> Void)
{
var quotes = [IndexQuotes]()
Alamofire.request(.GET, api_indexquotes).responseJSON { response in
if response.result.isSuccess && response.result.value != nil {
for photoDict in (response.result.value as! [NSDictionary]) {
let photo = IndexQuotes(dictionary: photoDict)
quotes.append(photo)
}
}
completion(quotes)
}
}
Or a bit "Swiftier"
... {
let allPhotos = response.result.value as! [NSDictionary]
quotes = allPhotos.map {IndexQuotes(dictionary: $0)}
}
I'd recommend also to use native Swift collection types rather than NSArray and NSDictionary
In viewDidLoad in your view controller call allQuotes and reload the table view in the completion block on the main thread.
The indexQuotes property starting with a lowercase letter is assumed to be the data source array of the table view
var indexQuotes = [IndexQuotes]()
override func viewDidLoad() {
super.viewDidLoad()
IndexQuotes.allQuotes { (quotes) in
self.indexQuotes = quotes
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
First of all call the function from inside the viewdidLoad. Secondly use blocks or delegation to pass the control back to ViewController. I would prefer the blocks approch. You can have completion and failure blocks. In completions block you can reload the views and on failure you can use alertcontroller or do nothing.
You can see AFNetworking as an example for blocks.
It's async action, just use a callback here:
class func allQuotes(callback: () -> Void) -> [IndexQuotes]
{
var quotes = [IndexQuotes]()
Alamofire.request(.GET, api_indexquotes).responseJSON { response in
if response.result.isSuccess && response.result.value != nil {
for i in (response.result.value as! [AnyObject]) {
let photo = IndexQuotes(dictionary: i as! NSDictionary)
quotes.append(photo)
}
}
callback()
}
return quotes
}
In your UIViewController:
var quotes = IndexQuotes.allQuotes() {
self.update()
}
var collectionView:UICollectionView!
override func viewDidLoad() {
update()
}
private func update() {
// Update collection view or whatever.
}
Actually, I strongly don't recommend to use class functions in this case (and many other cases too), it's not scalable and difficult to maintain after some time.