How to use a JSON file from an Onine server like turbo360? - swift

I am using a do catch scenario to try a JSONDecoder(), the only >problem is that I keep catching the error, but when I review my code I >can't see the error, maybe I need another set of eyes to help me out >of this one!
I placed my JSON file in a storage folder in turbo360, I've also tried gitHub, but neither is working, I believe.
import UIKit
class ViewController: UIViewController {
final let url = URL(string: "https://storage.turbo360.co/portfolio-website-hxoc6m/actors")
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
}
func downloadJson() {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("something is wrong")
return
}
print("downloaded")
print(downloadURL)
do
{
let decoder = JSONDecoder()
let actors = try decoder.decode(Actors.self, from: data)
print(actors)
} catch {
print("Something wrong after downloaded")
}
}.resume()
}
}
I supposed to get: JSONDonloadingSwift4.Actors
as confirmation that my JSON file has been accessed and decoded

Your JSON is invalid. You are missing an opening " on the last image URL. Fix that and as long as your Actor definition matches you should be good. jsonlint is very useful for checking JSON structure.

Related

Swift, URLSession downloading certain data in playgrounds, but not in Xcode project

I am trying to do some parsing of HTML on client side using Swift inside Xcode Project. I first tested this function inside playgrounds for a variety of URLs, and it downloads instantly for all my use cases. However, running this inside my Xcode project for iOS (even when disabling ATS in my info.plist), the URLSession will not download anything for many of the URLs to common websites that worked in playgrounds. It will still download some, such as the html of apple.com. Can anybody explain what I might be missing or need to enable/disable to get this to work.
func fetchHTMLString(url: URL) {
let task = URLSession.shared.downloadTask(with: url) { localURL, urlResponse, error in
if let localURL = localURL {
if let string = try? String(contentsOf: localURL) {
print("String here")
self.sortData(htmlString: string)
} else {
print("couldnt get as string")
}
}
}
task.resume()
print("going")
}
Update, I attempted to change this function to use URLSession data task, and was able to successfully download. I am, however, now just curious to find out why this would allow the download to complete with data task and not with download task. Here's the code that works
func fetchHTMLString(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error \(error.localizedDescription)")
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
if let string = String(data: data, encoding: .utf8) {
print(string)
self.sortData(htmlString: string)
print("String here")
} else {
print("couldn't get as string")
}
} else {
print("Error \(response.statusCode)")
}
} else {
print("No data or error returned.")
}
}
task.resume()
print("going")
}
```

Refer to variable from do block in catch

I need to access a variable inside a do statement. Will it behave like the if-else statement in the sense that you con't use variables outside of the if statement?
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
do {
//let url = URL?("https://www.hackingwithswift.com")
let TECIONEXContent = try String(contentsOf: URL("https://www.hackingwithswift.com"))
} catch { print("error")}
//I need to access TECIONEXContent variable outside the do statement
// Error: Use of unresolved identifier 'TECIONEXContent'
var TECGrid = TECIONEXContent.components(separatedBy: "\n")
}
}
The error is on the last line, 'unresolved identifier'.
Will it behave like the if-else statement in the sense that you con't use variables outside of the if statement?
Yes. But just like an if-else statement, you can define the variable before the do-catch:
E.g. in an if-else statement:
let foo: String
if bar > 1 {
foo = "bigger than one"
} else {
foo = "one or smaller"
}
Or, in your case:
let url = URL(string: "https://www.hackingwithswift.com")!
let contents: String
do {
contents = try String(contentsOf: url)
} catch {
print(error)
return
}
let grid = contents.components(separatedBy: "\n")
Or, you aren’t really doing anything with the error message, you can eliminate the do-catch altogether:
guard let contents = try? String(contentsOf: url) else {
print("error")
return
}
let grid = contents.components(separatedBy: "\n")
Frankly, all of that having been said, using String(contentsOf:) is probably not the best pattern, anyway, because that performs a synchronous network request, which risks having the OS “watchdog” process kill your app unceremoniously if the main thread is blocked; and even if that doesn’t happen, it’s not a good user experience to freeze the app while the network request is in progress. Usually we’d use URLSession:
let url = URL(string: "https://www.hackingwithswift.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
let string = String(data: data, encoding: .utf8) else {
print(error ?? "Unknown error")
return
}
guard 200 ..< 300 ~= httpResponse.statusCode else {
print("Expected 2xx response, but got \(httpResponse.statusCode)")
return
}
let grid = string.components(separatedBy: "\n")
DispatchQueue.main.async {
// use `grid` here
}
}.resume()
Unrelated, but:
The convention is to start variable names with lowercase letters.
You implemented loadView. It’s rare that we do that, and instead we implement viewDidLoad, making sure to call super.viewDidLoad(), too.
If you’re doing this in a playground, you’d obviously also set needsIndefiniteExecution, if you haven’t already.
Thus:
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
performRequest()
}
func performRequest() {
let url = URL(string: "https://www.hackingwithswift.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let data = data,
let httpResponse = response as? HTTPURLResponse,
let string = String(data: data, encoding: .utf8) else {
print(error ?? "Unknown error")
return
}
guard 200 ..< 300 ~= httpResponse.statusCode else {
print("Expected 2xx response, but got \(httpResponse.statusCode)")
return
}
let grid = string.components(separatedBy: "\n")
DispatchQueue.main.async {
print(grid)
// use `grid` here
}
}.resume()
}
}
PlaygroundPage.current.liveView = ViewController()
PlaygroundPage.current.needsIndefiniteExecution = true
In your code are four(!) very bad practices.
Never load data synchronously with API like String(contentsOf from a remote URL. Use asynchronous API like URLSession.
Never print a meaningless literal string in a catch block. Print the error instance.
According to the naming convention variable names should be lowerCamelCased.
In a do - catch block put always all good code in the do scope. This solves your issue.
do {
let tecionexContent = try String(contentsOf: URL("https://www.hackingwithswift.com")!)
let tecGrid = tecionexContent.components(separatedBy: "\n")
} catch { print(error) }
recommended
URLSession.shared.dataTask(with: URL("https://www.hackingwithswift.com")!) { data, _ , error in
if let error = error { print(error); return }
let tecionexContent = String(data: data!, encoding: .utf8)!
let tecGrid = tecionexContent.components(separatedBy: "\n")
}.resume()
The Issue
The problem with your code is that you are defining the variable inside a block/closure, which is a local scope. Your variable should be at a scope where both the blocks are able to see it. This means that in the catch block, the variable does not exist. Specifically you had attempted to reference the string variable you called TECIONEXContent from outside of the closure in which it was instantiated.
On some general style points: please stick with the swift naming convention for variables (i.e. camel case) whereas your classes etc should be capitalized. (For the purposes of running the below code in the playground I've used an arbitrary function name, but you could employ it from your lifecycle methods).
Basic Playground Demo Code
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
private var tecGrid: [String]? {
didSet {
// Update some UI Here (insuring your on the main thread)
print(self.tecGrid)
}
}
func test() {
do {
var texionicContent = try String(contentsOf:URL(string: "https://www.hackingwithswift.com")!)
tecGrid = texionicContent.components(separatedBy: "\n")
}
catch let error {
print("Catch the error")
}
}
}
let play = MyViewController(nibName: nil, bundle: nil)
play.test()

Why is Xcode complaining when I wrap this let in an if statement?

I have the following working code in my app:
func downloadJSON(completed: #escaping ([JsonFile.JsonBonuses]?) -> ()) {
let url = URL(string: "http://example.com/ExampleData.json")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error == nil, let data = data {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data)
completed(posts.bonuses)
self.defaults.set(posts.meta.version, forKey: "jsonVersion")
print("URLSession did not fail")
print("JSON Version Set to \(posts.meta.version)")
} catch {
print("Can't decode JSON: \(error)")
}
} else {
print("downloadJSON completed")
completed(nil)
}
}.resume()
}
I am wanting to change that URL to a different one based on a UserDefaults setting. So I wrapped the let url in an if statement like this:
if devModeStatus == true {
let url = URL(string: "https://otherexample.com/Example2Data.json")!
} else if devModeStatus == false {
let url = URL(string: "http://example.com/ExampleData.json")!
} else {
print("Invalid Dev Status encountered!")
return
}
However when I do that, Xcode complains about "Use of unresolved identifier 'url'; did you mean 'erfl'?" on the line that says URLSession.shared.dataTask(with: url) { (data, response, error) in
I'm not sure why it is complaining about this change. I use that same if/else logic else where to print a status message at first load of this view, so I know the variable is correct.
Your url declaration dies within those if, else-if scopes. You need to declare your url first then modify it. Also, since devModeStatus is a boolean value, the else case will never be reached, so no need for third path. Update your code as following:
let url: URL
if devModeStatus {
url = URL(string: "https://otherexample.com/Example2Data.json")!
} else {
url = URL(string: "http://example.com/ExampleData.json")!
}

getting output from URLSession in XCUITest

I'm trying to grab a value from a URL inside a XCUI Test. However it's not outputting so I'm not sure if there's anything I'm supposed to be doing aside from I've already tried in the code below:
import XCTest
class ExampleUITests: XCTestCase {
func testExample() {
print("We are in XCUITest textExample right now")
let urlString = "https://api.ipify.org/"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data
else {
print("error found in data")
return
}
print("*******")
let outputStr = String(data: data, encoding: String.Encoding.utf8) as String!
print (outputStr)
}.resume()
}
}
The problem is, response from server is returned after test is finished. You should add waiting for network response in the test.
At the start of the test add:
let expectation = expectationWithDescription("")
At the end of the test add:
waitForExpectationsWithTimeout(5.0) { (error) in
if error != nil {
XCTFail(error.localizedDescription)
}
}
And in the network completion block add:
expectation.fulfill()
You can get more info about waining for async task in the test for example here How can I get XCTest to wait for async calls in setUp before tests are run?.

URLSession in loop not working

I am trying to downlod multiple JSON files with a URLSession and when I run the funtion one time it works. But the moment I call the getSMAPrices function from a loop it does not work and I can not find out why.
Here is the working download function that works if i call it.
func getSMAPrices(symbol: String) {
let urlString = "https://www.alphavantage.co/query?function=SMA&symbol=\(symbol)&interval=daily&time_period=9&series_type=close&apikey=KPLI12AW8JDXM77Y"
guard let url = URL(string: urlString) else {
return
}
dataTask = defaultSession.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else {
return
}
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let stockData = try decoder.decode(SimpelMovingAvarage.self, from: data)
//Get back to the main queue
DispatchQueue.main.async {
print(stockData)
}
} catch let jsonError {
print(jsonError)
}
})
dataTask?.resume()
}
And here is my very simple loop that replaces a part in the URL every run cycle. But nothing happens.
public func scanSymbols() {
for symbol in self.symbols {
progress += 1
progresBar.maxValue = Double(symbols.count)
progresBar.doubleValue = progress
//This does not work
getSMAPrices(symbol: symbol.key)
}
}
It's because your dataTask variable appears to be an instance property and is getting overwritten every time you call this method.