1

EDIT: The code is now working, turns out it was stale data. I saved and cleaned the project, reset the simulator and now it's working! Thanks @pbasdf for helping me out!

I'm still getting used to both core data and localized strings so I'm not sure if I'm using localized strings incorrectly, or if the issue is somewhere else, but when I tap on the cells I can see via my print statement that all the values are nil, and obviously no web pages are loaded.

I'm using a localized.strings file to equate a name to its corresponding URL:

"iPhone" = "http://www.apple.com/iphone/";
"iPad" = "http://www.apple.com/ipad/";
"Macbook" = "http://www.apple.com/macbook/";
"Google" = "https://www.google.com/";
"Firebase" = "https://firebase.google.com/";
"Magic Leap" = "https://www.magicleap.com/";
"Facebook" = "https://www.facebook.com/";
"Instagram" = "https://www.instagram.com/?hl=en";
"WhatsApp" = "https://www.whatsapp.com/";
"Model S" = "https://www.tesla.com/models";
"Model X" = "https://www.tesla.com/modelx";
"Powerwall" = "https://www.tesla.com/powerwall";
"Twitter" = "https://twitter.com/?lang=en";
"Periscope" = "https://www.periscope.tv/";
"Vine" = "https://vine.co/";

So I can set the url attribute (of type String) of my Product entity. Dictionary of companies and products:

let defaultProducts = ["Apple" : ["iPhone", "iPad", "Macbook"],
                               "Google" : ["Google", "Firebase", "Magic Leap"],
                               "Facebook" : ["Facebook", "Instagram", "WhatsApp"],
                               "Tesla" : ["Model S", "Model X", "Powerwall"],
                               "Twitter" : ["Twitter", "Periscope", "Vine"]]

And here's the relevant code from my function:

let companyProducts = defaultProducts[name]

for productName in companyProducts! {

    let product = NSManagedObject(entity: productEntity, insertInto:managedContext)
    let names = NSLocalizedString(productName, comment:"")

    product.setValue(productName, forKey: "name")
    product.setValue(productName, forKey: "image")
    product.setValue(names, forKey: "url")

    product.setValue(company, forKey: "company")
}

Then I use it to display the web page when the cell is tapped

// WebView
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    guard let appDelegate =
        UIApplication.shared.delegate as? AppDelegate else {
            return
    }

    let managedContext =
        appDelegate.persistentContainer.viewContext

    let entity =
        NSEntityDescription.entity(forEntityName: "Product",
                                   in: managedContext)!

    let product = NSManagedObject(entity: entity,
                                  insertInto: managedContext)

    if let url = product.value(forKey: "url") as? URL {
        webView.load(URLRequest(url: url))
        webView.allowsBackForwardNavigationGestures = true
        view = webView

    }
    print(product.value(forKey: "url") as? URL)
}

Am I doing something incorrectly in localized.strings, resulting in the nil value?

EDIT: setDefaults() with url dictionary:

func setDefaults() {
    let userDefaults = UserDefaults.standard
    let defaultValues = ["firstRun" : true]
    userDefaults.register(defaults: defaultValues)

    if userDefaults.bool(forKey: "firstRun") {
        let defaultProducts = ["Apple" : ["iPhone", "iPad", "Macbook"],
                               "Google" : ["Google", "Firebase", "Magic Leap"],
                               "Facebook" : ["Facebook", "Instagram", "WhatsApp"],
                               "Tesla" : ["Model S", "Model X", "Powerwall"],
                               "Twitter" : ["Twitter", "Periscope", "Vine"]]

        let urlDictionary = ["iPhone" : "http://www.apple.com/iphone/",
                           "iPad" : "http://www.apple.com/ipad/",
                           "Macbook" : "http://www.apple.com/macbook/",
                           "Google" : "https://www.google.com/",
                           "Firebase" : "https://firebase.google.com/",
                           "Magic Leap" : "https://www.magicleap.com/",
                           "Facebook" : "https://www.facebook.com/",
                           "Instagram" : "https://www.instagram.com/?hl=en",
                           "WhatsApp" : "https://www.whatsapp.com/",
                           "Model S" : "https://www.tesla.com/models",
                           "Model X" : "https://www.tesla.com/modelx",
                           "Powerwall" : "https://www.tesla.com/powerwall",
                           "Twitter" : "https://twitter.com/?lang=en",
                           "Periscope" : "https://www.periscope.tv/",
                           "Vine" : "https://vine.co/"]

        let companyEntity = NSEntityDescription.entity(forEntityName: "Company", in: managedContext)!
        let productEntity = NSEntityDescription.entity(forEntityName: "Product", in: managedContext)!

        // Setting the default company data (name, logo, and stockPrice)
        let url = URL(string: "https://query.yahooapis.com/v1/public/yql?q=select%20symbol%2C%20Ask%2C%20YearHigh%2C%20YearLow%20from%20yahoo.finance.quotes%20where%20symbol%20in%20(%22AAPL%22%2C%22GOOG%22%2C%22TWTR%22%2C%22TSLA%22%2C%20%22FB%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys")!
        let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
            if error != nil {
                print(error!)
            } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 {
                let json = JSON(data: data!)
                if let quotes = json["query"]["results"]["quote"].array {
                    for quote in quotes {
                        let symbol = quote["symbol"].stringValue
                        let name = NSLocalizedString(symbol, comment:"")
                        let ask = quote["Ask"].stringValue
                        let company = NSManagedObject(entity: companyEntity,insertInto: managedContext)
                        company.setValue(name, forKey: "name")
                        company.setValue(name, forKey: "logo")
                        company.setValue(ask, forKey: "stockPrice")
                        companies.append(company)

                        let companyProducts = defaultProducts[name]

                        for productName in companyProducts! {

                            let product = NSManagedObject(entity: productEntity, insertInto:managedContext)
                            let names = urlDictionary[productName]

                            product.setValue(productName, forKey: "name")
                            product.setValue(productName, forKey: "image")
                            product.setValue(names, forKey: "url")

                            product.setValue(company, forKey: "company")

                        }
                        print(json)
                    }

                    DispatchQueue.main.async {
                        do {
                            try managedContext.save()
                            vc.tableView.reloadData()
                            userDefaults.set(false, forKey: "firstRun")
                        } catch let error as NSError {
                            print("Could not save. \(error), \(error.userInfo)")
                        }
                    }
                }
            } else {
                print("The data couldn't be loaded")
            }
        }
        task.resume()
    }
}
d0xi45
  • 155
  • 2
  • 15
  • In `tableView:didSelectRowAt:` you are creating a new (blank) Product and then getting its url attribute - which is therefore nil. You need instead to look up the correct product for the row that has been tapped: `let product = products[indexPath.row]`. – pbasdf Jan 28 '17 at 16:17
  • @pbasdf That's a silly mistake, I should have noticed what I was doing there - I've changed it to your suggestion and I'm still seeing `nil` however. I'll try to get rid of the `localized.strings` approach since apparently it's not the most efficient way of doing this, and perhaps that's causing the problem? – d0xi45 Jan 29 '17 at 19:45
  • If you `print(product.value(forKey: "url"))`, does it show nil, or the url? – pbasdf Jan 29 '17 at 20:30
  • It shows nil each time I tap on one of the product cells – d0xi45 Jan 29 '17 at 21:13
  • To test, change `let names = NSLocalizedString(productName, comment:"")` to `let names = "http://www.apple.com/iphone/"`. If it works, the problem must be in the localizedStrings; if not, the problem is in your didSelectRowAt code. – pbasdf Jan 29 '17 at 21:21
  • No luck with that unfortunately, although removing `as? URL` from the print statement prints Optional() instead of nil. – d0xi45 Jan 29 '17 at 21:25
  • The `url` attribute of your Product entity is defined as a String. So when you try to cast it as a url (with `as? URL`) it returns nil. Cast it to String (`if let urlString = product.value(forKey: "url") as? String`). Then create a URL with that string (`let url = URL(string:urlString)`). – pbasdf Jan 29 '17 at 21:32
  • Ok, got it.. dealing with optionals can be confusing sometimes! Using this approach, tapping on the cell prints the same thing to the console and the web page doesn't load, but it does show a white screen instead of not changing screens at all, which I guess is progress? Do you think the `localized.strings` approach is slowing me down, even if it's not causing the core problem? Since I'm still learning this stuff maybe I should nip it in the bud and just try doing this a different way. – d0xi45 Jan 29 '17 at 21:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/134325/discussion-between-pbasdf-and-d0xi45). – pbasdf Jan 29 '17 at 21:41

1 Answers1

1

A number of problems were compounded:

  1. The didSelectRowAt code was creating new Product instances, rather than using the correct Product based on the row tapped. The fix for this was to replace:

    let product = NSManagedObject(entity: entity, insertInto: managedContext)
    

    with:

    let product = products[indexPath.row]
    
  2. The code also assumed that the url attribute of the Product entity was of type URL; in fact it was of type String. The fix for this was to correct the cast, and then create a URL from the String:

    if let urlString = product.value(forKey: "url") as? String {
        let url = URL(string:urlString)
    
  3. Finally, the NSLocalizedStrings were causing problems when creating the default data. That was fixed by replacing them with a dictionary:

    let urlDictionary = ["iPhone" : "http://www.apple.com/iphone/", 
                         "iPad" : "http://www.apple.com/ipad/", etc
    

    and then:

    let names = urlDictionary[productName]
    
pbasdf
  • 21,386
  • 4
  • 43
  • 75