2017-06-24 4 views
1

J'essaie de passer un objet que j'ai créé appelé detail à partir d'un fichier rapide nommé RestManager.swift dans un ViewController. L'objet contient tous les éléments mais quand je l'appelle dans mon vue controller il est vide. De ce que j'ai rassemblé en ligne cela peut avoir quelque chose à voir avec URLSession travaillant sur un thread d'arrière-planPasser un objet de URLSession à ViewController

Mon RestManager.swift ressemble à ceci.

class RestManager { 


func reqDetails(id: Int) { 
    // Create URL 
    let id = String(id) 
    let url = "https://website.example.com/" 
    let url = URL(string: url + id)! 

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in 

     if error != nil 
     { 
      print ("ERROR") 
     } 

     else 
     { 
      if let content = data 
      { 

        let jsonData = JSON(data: content) 

        let id = jsonData["id"].intValue 
        let name = jsonData["title"]["rendered"].string! 
        let link = jsonData["link"].url! 
        let content = jsonData["content"]["rendered"].string! 


        // Create Object 
        let detail = Detail(id: id, name: name, content: content, thumbnailUrl: link) 
        self.details.append(detail) 

      } 
     } 
    } 
    task.resume() 
} 
} 

Mon View Controller ressemble donc:

class DetailViewController: UIViewController { 

var ListingID = Int() 
let restManager = RestManager() 

@IBOutlet weak var ContentLabel: UILabel! 

override func viewDidLoad() { 
    super.viewDidLoad() 
    restManager.reqDetails(id: ListingID) 
    ContentLabel.text? = restManager.details[0].name // After running the app this index value is out of range. 


} 

.. 

} 

Répondre

3

Utilisez la fonction plus en pour transmettre des données comme celui-ci

'

func reqDetails(id: Int,completionHandler:@escaping (_ detilObject:Detail)->Void) { 
    // Create URL 
    let id = String(id) 
    let url = "https://website.example.com/" 
    let url = URL(string: url + id)! 

    let task = URLSession.shared.dataTask(with: url) { (data, response, error) in 

     if error != nil 
     { 
      print ("ERROR") 
     } 

     else 
     { 
      if let content = data 
      { 

        let jsonData = JSON(data: content) 

        let id = jsonData["id"].intValue 
        let name = jsonData["title"]["rendered"].string! 
        let link = jsonData["link"].url! 
        let content = jsonData["content"]["rendered"].string! 


        // Create Object 
        let detail = Detail(id: id, name: name, content: content, thumbnailUrl: link) 
        self.details.append(detail) 
        completionHandler(self.details) 

      } 
     } 
    } 
    task.resume() 
} 

'

et appelez votre fonction comme celui-ci. «

restManager.reqDetails(id: ListingID , completionHandler: { (detail) in 
       // here is your detail object 
      }) 

»

0

Vous avez raison que votre problème est dû à une tentative de lire la réponse immédiatement (comme il est très peu probable d'être prêt immédiatement). Vous avez besoin d'un moyen d'informer votre contrôleur (sur le thread/file d'attente principal!) Lorsque vos données sont prêtes (ou qu'une erreur est survenue).

Une simple si la solution inélégante serait d'ajouter un weak var controller: DetailViewController à votre RestManager (faible, donc il ne provoque pas un cycle retain) qui est passé sur init (RestManager(controller: self)) et utiliser cette référence dans la fermeture de la tâche URL pour indiquer le contrôleur c'est prêt ou erré.

Vous devez probablement utiliser des notifications ou le modèle délégué pour éviter le couplage étroit du RestManager à DetailViewController.

+0

Vous pouvez utiliser processus de délégation pour récupérer le rappel avec votre tableau 'details' en tant que paramètre dans votre viewcontroller.Maintenant, regardez comment implémenter le délégué avec le protocole dans swift. – Tann

0

Salut, je pense que vous venez de rencontrer l'écueil typique de la programmation asynchrone. Ce que vous faites, c'est demander la valeur de retour de la tâche, avant qu'elle ne revienne de l'URLSession. Vous pouvez le résoudre en créant vous-même un gestionnaire de complétion comme dans cet exemple, où je reçois des données météo de Darksky. Notez que vous n'avez même pas besoin de faire une classe pour cela, juste une fonction. PS: j'utilise Alamofire, SwiftyJSON et Gloss, ce qui réduit considérablement la complexité en utilisant les interfaces REST. Et voici Swift 3!

import Alamofire 
import SwiftyJSON 
import Gloss 


typealias DarkSkyWeatherForecast = (_ json : Gloss.JSON?, _ error : Error?) -> Void 


func getWeatherForcast(latitude:Double, longitude:Double, completionHandler:@escaping DarkSkyWeatherForecast) -> Void { 

    let urlString = "https://api.darksky.net/forecast/"+darkSkyKey+"/"+String(latitude)+","+String(longitude)+"?units=si" 

    Alamofire.request(urlString).responseJSON { (response) in 
     if let resp = response.result.value { 
      let json = JSON(resp) 
      let glossJson = json.dictionaryObject 
      completionHandler(glossJson, nil) 
     }else{ 
      completionHandler(nil, response.error) 
     } 
    } 
} 

Et l'appel de la fonction comme ceci:

getWeatherForcast(latitude: lat, longitude: lon) { (jsonArg, error) in 
      if error == nil{ 
       guard let weather = DarkSkyWeather(json: jsonArg!) else { 
        self.effectView.removeFromSuperview() 
        return 
       } 
       if let ambTemp = weather.currently?.temperature, let windspd = weather.currently?.windSpeed, let windDir = weather.currently?.windBearing{ 
        self.effectView.removeFromSuperview() 
        self.ambTemperature.text = String(ambTemp) 
        self.windSpeed.text = String(windspd) 
        self.windDirection.text = String(windDir) 
        self.getSpeed() 
       } 
      }else{ 

       self.effectView.removeFromSuperview() 
       let alert = UIAlertController(title: "Error", message: "Could not get weather forecast", preferredStyle: .alert) 
       let okAction = UIAlertAction(title: "OK", style: .default, handler: { (action) in 
        self.dismiss(animated: true, completion: nil) 
       }) 
       alert.addAction(okAction) 
       self.present(alert, animated: true, completion: nil) 
      } 
     } 

Remarque, je ne remplir que les champs de texte une fois que le gestionnaire d'achèvement est terminé et retourne en fait soit des données ou une erreur. Ne tenez pas compte de la substance « effectView », qui est une curser spéciale attente spinner :-)

Et aussi de connaître ces struct qui est mis en correspondance avec les données JSON pourrait être helpfull:

//common struct for weather data 
public struct WeatherDataStruct : Decodable{ 
    let time : Int? 
    let summary : String? 
    let icon : String? 
    let precipIntensity : Double? 
    let precipProbability : Double? 
    let precipType : String? 
    let temperature : Double? 
    let apparentTemperature : Double? 
    let dewPoint : Double? 
    let humidity: Double? 
    let windSpeed : Double? 
    let windBearing : Int? 
    let visibility : Double? 
    let cloudCover : Double? 
    let pressure : Double? 
    let ozone : Double? 

    public init?(json: JSON){ 
     self.time = "time" <~~ json 
     self.summary = "summary" <~~ json 
     self.icon = "icon" <~~ json 
     self.precipIntensity = "precipIntensity" <~~ json 
     self.precipProbability = "precipProbability" <~~ json 
     self.precipType = "precipType" <~~ json 
     self.temperature = "temperature" <~~ json 
     self.apparentTemperature = "apparantTemperature" <~~ json 
     self.dewPoint = "dewPoint" <~~ json 
     self.humidity = "humidity" <~~ json 
     self.windSpeed = "windSpeed" <~~ json 
     self.windBearing = "windBearing" <~~ json 
     self.visibility = "visibility" <~~ json 
     self.cloudCover = "cloudCover" <~~ json 
     self.pressure = "pressure" <~~ json 
     self.ozone = "ozone" <~~ json 
    } 
} 

//hourly weather struct 
public struct HourlyStruct : Decodable{ 
    let summary : String? 
    let icon : String? 
    let data : [WeatherDataStruct]? 

    public init?(json: JSON) { 
     self.summary = "summary" <~~ json 
     self.icon = "icon" <~~ json 
     self.data = "data" <~~ json 
    } 
} 

//total struct for the whole json answer from darksky weather 
public struct DarkSkyWeather : Decodable{ 
    let latitude : Double? 
    let longitude : Double? 
    let timezone : String? 
    let offset : Int? 
    let currently : WeatherDataStruct? 
    let hourly : HourlyStruct? 

    public init?(json: JSON) { 
     self.latitude = "latitude" <~~ json 
     self.longitude = "longitude" <~~ json 
     self.timezone = "timezone" <~~ json 
     self.offset = "offset" <~~ json 
     self.currently = "currently" <~~ json 
     self.hourly = "hourly" <~~ json 
    } 
}