Search code examples
jsonswiftalamofireyahoo-api

How can I put JSON data into a UILabel using AlamoFire and Swift


I am trying to put some data from the JSON Yahoo api into some UILabels. When I try to run my project I am greeted with a Thread 1 error.

Here is some of my code:

@IBOutlet weak var tableView: UITableView!
var stockSymbol: String = "AAPL"
var stock: [Stock] = []

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.dataSource = self
    tableView.delegate = self
    tableView.registerNib(UINib(nibName: "HomeCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: "homeCell")
    tableView.rowHeight = 60

    // Do any additional setup after loading the view.
    SwiftStockKit.fetchStockForSymbol(symbol: stockSymbol) { (stock) -> ()  in
    self.stock = [stock]
        self.tableView.reloadData()


    }
}

class func fetchStockForSymbol(symbol symbol: String, completion:(stock: Stock) -> ()) {

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

        let stockURL = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22AAPL%22)&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&format=json"

        Alamofire.request(.GET, stockURL).responseJSON { response in

            if let resultJSON = response.result.value as? [String : AnyObject]  {

                if let stockData = ((resultJSON["query"] as! [String : AnyObject])["results"] as! [String : AnyObject])["quote"] as? [String : AnyObject] {

                    // lengthy creation, yeah
                    var dataFields = [[String : String]]()
                    dataFields.append(["Ask" : stockData["Ask"] as? String ?? "N/A"])
                    dataFields.append(["Average Daily Volume" : stockData["AverageDailyVolume"] as? String ?? "N/A"])
                    dataFields.append(["Bid" : stockData["Bid"] as? String ?? "N/A"])
                    dataFields.append(["Book Value" : stockData["BookValue"] as? String ?? "N/A"])
                    dataFields.append(["Change" : stockData["Change"] as? String ?? "N/A"])
                    dataFields.append(["Percent Change" : stockData["ChangeinPercent"] as? String ?? "N/A"])
                    dataFields.append(["Day High" : stockData["DaysHigh"] as? String ?? "N/A"])
                    dataFields.append(["Day Low" : stockData["DaysLow"] as? String ?? "N/A"])
                    dataFields.append(["Div/Share" : stockData["DividendShare"] as? String ?? "N/A"])
                    dataFields.append(["Div Yield" : stockData["DividendYield"] as? String ?? "N/A"])
                    dataFields.append(["EBITDA" : stockData["EBITDA"] as? String ?? "N/A"])
                    dataFields.append(["Current Yr EPS Estimate" : stockData["EPSEstimateCurrentYear"] as? String ?? "N/A"])
                    dataFields.append(["Next Qtr EPS Estimate" : stockData["EPSEstimateNextQuarter"] as? String ?? "N/A"])
                    dataFields.append(["Next Yr EPS Estimate" : stockData["EPSEstimateNextYear"] as? String ?? "N/A"])
                    dataFields.append(["Earnings/Share" : stockData["EarningsShare"] as? String ?? "N/A"])
                    dataFields.append(["50D MA" : stockData["FiftydayMovingAverage"] as? String ?? "N/A"])
                    dataFields.append(["Last Trade Date" : stockData["LastTradeDate"] as? String ?? "N/A"])
                    dataFields.append(["Last" : stockData["LastTradePriceOnly"] as? String ?? "N/A"])
                    dataFields.append(["Last Trade Time" : stockData["LastTradeTime"] as? String ?? "N/A"])
                    dataFields.append(["Market Cap" : stockData["MarketCapitalization"] as? String ?? "N/A"])
                    dataFields.append(["Company" : stockData["Name"] as? String ?? "N/A"])
                    dataFields.append(["One Yr Target" : stockData["OneyrTargetPrice"] as? String ?? "N/A"])
                    dataFields.append(["Open" : stockData["Open"] as? String ?? "N/A"])
                    dataFields.append(["PEG Ratio" : stockData["PEGRatio"] as? String ?? "N/A"])
                    dataFields.append(["PE Ratio" : stockData["PERatio"] as? String ?? "N/A"])
                    dataFields.append(["Previous Close" : stockData["PreviousClose"] as? String ?? "N/A"])
                    dataFields.append(["Price-Book" : stockData["PriceBook"] as? String ?? "N/A"])
                    dataFields.append(["Price-Sales" : stockData["PriceSales"] as? String ?? "N/A"])
                    dataFields.append(["Short Ratio" : stockData["ShortRatio"] as? String ?? "N/A"])
                    dataFields.append(["Stock Exchange" : stockData["StockExchange"] as? String ?? "N/A"])
                    dataFields.append(["Symbol" : stockData["Symbol"] as? String ?? "N/A"])
                    dataFields.append(["200D MA" : stockData["TwoHundreddayMovingAverage"] as? String ?? "N/A"])
                    dataFields.append(["Volume" : stockData["Volume"] as? String ?? "N/A"])
                    dataFields.append(["52w High" : stockData["YearHigh"] as? String ?? "N/A"])
                    dataFields.append(["52w Low" : stockData["YearLow"] as? String ?? "N/A"])



                    let stock = Stock(

                        ask: dataFields[0].values.first,
                        averageDailyVolume: dataFields[1].values.first,
                        bid: dataFields[2].values.first,
                        bookValue: dataFields[3].values.first,
                        changeNumeric: dataFields[4].values.first,
                        changePercent: dataFields[5].values.first,
                        dayHigh: dataFields[6].values.first,
                        dayLow: dataFields[7].values.first,
                        dividendShare: dataFields[8].values.first,
                        dividendYield: dataFields[9].values.first,
                        ebitda: dataFields[10].values.first,
                        epsEstimateCurrentYear: dataFields[11].values.first,
                        epsEstimateNextQtr: dataFields[12].values.first,
                        epsEstimateNextYr: dataFields[13].values.first,
                        eps: dataFields[14].values.first,
                        fiftydayMovingAverage: dataFields[15].values.first,
                        lastTradeDate: dataFields[16].values.first,
                        last: dataFields[17].values.first,
                        lastTradeTime: dataFields[18].values.first,
                        marketCap: dataFields[19].values.first,
                        companyName: dataFields[20].values.first,
                        oneYearTarget: dataFields[21].values.first,
                        open: dataFields[22].values.first,
                        pegRatio: dataFields[23].values.first,
                        peRatio: dataFields[24].values.first,
                        previousClose: dataFields[25].values.first,
                        priceBook: dataFields[26].values.first,
                        priceSales: dataFields[27].values.first,
                        shortRatio: dataFields[28].values.first,
                        stockExchange: dataFields[29].values.first,
                        symbol: dataFields[30].values.first,
                        twoHundreddayMovingAverage: dataFields[31].values.first,
                        volume: dataFields[32].values.first,
                        yearHigh: dataFields[33].values.first,
                        yearLow: dataFields[34].values.first,
                        dataFields: dataFields


                    )
                    dispatch_async(dispatch_get_main_queue()) {
                        completion(stock: stock)
                    }
                }
            }
        }
    }
}

This is the Struct:

struct Stock {

    var ask: String?
    var averageDailyVolume: String?
    var bid: String?
    var bookValue: String?
    var changeNumeric: String?
    var changePercent: String?
    var dayHigh: String?
    var dayLow: String?
    var dividendShare: String?
    var dividendYield: String?
    var ebitda: String?
    var epsEstimateCurrentYear: String?
    var epsEstimateNextQtr: String?
    var epsEstimateNextYr: String?
    var eps: String?
    var fiftydayMovingAverage: String?
    var lastTradeDate: String?
    var last: String?
    var lastTradeTime: String?
    var marketCap: String?
    var companyName: String?
    var oneYearTarget: String?
    var open: String?
    var pegRatio: String?
    var peRatio: String?
    var previousClose: String?
    var priceBook: String?
    var priceSales: String?
    var shortRatio: String?
    var stockExchange: String?
    var symbol: String?
    var twoHundreddayMovingAverage: String?
    var volume: String?
    var yearHigh: String?
    var yearLow: String?

    var dataFields: [[String : String]]

}

and I also have a different stockurl my swiftStockKit file which may impede with the ability to display the data to a UILabel

let stockURL = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22AAPL%22)&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&format=json"

and the rest is the same.


Solution

  • A few suggestions:

    • Yahoo offers https so use that
    • You don't need to call dispatch_async before the Alamofire request. The request will be performed on a background thread by default.
    • Since you are mapping a long list of fields, it's better to use a JSON-to-object mapping framework like ObjectMapper. You can have the fields in their native data types rather than strings.
    • I can't help with the "thread 1 error" since I don't see your tableView:cellForRowAtIndexPath: method. So the rest of this answer is to focus on the JSON handling

    You need to slightly redefine your Stock struct (edited for brevity):

    import Foundation
    import ObjectMapper
    
    struct Stock: Mappable {
        var symbol: String?
        var ask: Double?
        var lastTradeDate: NSDate?
        // other fields...
    
        var dataFields = [[String : String]]()
    
        init?(_ map: Map) {
            // Perform validation of the JSON here
            // Leave blank if you don't need to validate anything
        }
    
        mutating func mapping(map: Map) {
            symbol          <- map["query.results.quote.symbol"] // maps to a String
            ask             <- map["query.results.quote.Ask"]    // maps to a Double
    
            // You probably want to combine this with LastTradeTime
            // but I'm keeing them as separate for now
            lastTradeDate   <- (map["query.results.quote.LastTradeDate"], MyDateTransform())
        }
    }
    
    // We need a custom date formatter since Yahoo doesn't
    // return the standard format of yyyy-MM-dd
    class MyDateTransform : TransformType {
        typealias Object = NSDate
        typealias JSON = String
    
        static let dateFormatter = { Void -> NSDateFormatter in
            let formatter = NSDateFormatter()
            formatter.dateFormat = "M/d/yyyy"
    
            return formatter
        }()
    
        func transformFromJSON(value: AnyObject?) -> NSDate? {
            if let stringValue = value as? String {
                return MyDateTransform.dateFormatter.dateFromString(stringValue)
            } else {
                return nil
            }
        }
    
        func transformToJSON(value: NSDate?) -> String? {
            if let dateValue = value {
                return MyDateTransform.dateFormatter.stringFromDate(dateValue)
            } else {
                return nil
            }
        }
    }
    

    Usage:

    let stockURL = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22AAPL%22)&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&format=json"
    
    Alamofire.request(.POST, stockURL)
        .validate()
        .responseJSON { response in
            guard response.result.isSuccess else {
                print(response.result.error!)
                return
            }
    
            // This is all it takes to initiate a new object from JSON
            let stock = Mapper<Stock>().map(response.result.value)
        }