import UIKit
let cellid = "cell"
class Post {
var videoName: String?
var videoDescription: String?
}
class VideoFeedController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
var posts = [Post]()
var json: [Any]?
var names: [String] = []
var contacts: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let CatagoryMain = Post()
CatagoryMain.videoDescription = "example text"
CatagoryMain.videoName = "wewewewew"
posts.append(CatagoryMain)
collectionView?.backgroundColor = UIColor.white
navigationItem.title = "Main Video Feed"
collectionView?.alwaysBounceVertical = true
collectionView?.register(VideoFeedCell.self, forCellWithReuseIdentifier: cellid)
let urlString = "example url"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("failed")
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: []) as! [String:Any]
let currentVideo = parsedData["video"] as! [String:Any]
if let currentVideoTitle = currentVideo["title"] as? String {
print(currentVideoTitle)
// have text display instead of wewewew and example text
}
} catch let error as NSError {
print(error)
}
}
}.resume()
}
So for now it says example text for the video description and wewewewew for the video name and those display fine.I am trying to get the info from the json though so instead of example text it would have the description and instead of wewewewew it would have the video name I thought it would go like this
if let currentVideoTitle = currentVideo["title"] as? String {
print(currentVideoTitle)
CatagoryMain.videoDescription = "\(currentVideoTitle)"
CatagoryMain.videoName = "\(currentVideoTitle)"
posts.append(CatagoryMain)
}
however that was wrong and if I do that nothing shows on the screen besides the navigation bar and the title for the navigation bar. Thank you in advance if you can help! The actual title does show in the console by the way.
Like the comment I left above, we need more code to figure out the situation and help you out :) And your question is a little bit unclear.
however that was wrong and if I do that nothing shows on the screen
besides the navigation bar and the title for the navigation bar.
You mean the UICollectionView is not loading the data? or the data are not loaded into posts array? is collectionView even init-ed?
Also, would like to have a look of the parsedData object. As you are using collectionView which means you want to display multiple cells. But your parsing codes do not look like you are parse an array of objects.(but only one object??)
Quick advice,
1. Print out the posts.counts to see if there are data?
2. If there is any data, add collectionView.reloadData() after you added data to posts.
Related
I am working on iOS (Swift) application. I am getting some server response like below.
"description":"This is sample text to show in UI. When doing everyday activities.\u003cbr /\u003eclass is a strong predictor of life, and again sample text here.\u003cbr /\u003eSample text can show here also."
So, Above text has 3 paragraphs, I am trying to displaying them in Textview, But it is showing in plain with new line instead of New Paragraph.
override func viewDidLoad() {
super.viewDidLoad()
let description = jsonResponse["description"] as! String
self.textView.attributedText = description.htmlAttributedString()
}
extension String {
func htmlAttributedString() -> NSAttributedString? {
guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
guard let html = try? NSMutableAttributedString(
data: data,
options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil) else { return nil }
return html
}
}
The issue is it is showing text, But like new line showing instead of new paragraph. How to fix this?
I have fixed this issue by following, And after conversion server response into json serialization, the special characters code showing as
So, I have fixed like below
let description = jsonResponse["description"] as! String
let formattedString = description.replacingOccurrences(of: "<br />", with: " \n\n")
self.textView.text = formattedString
I am attempting to make a Swift application with two main focuses. One is to display all the data from an URL in a ScrollView and the other is a button to get a random name of a game.
The button works and I get a random game but when I try to load the application with the UIScrollView, I get a SIGABRT on line 33.
Any help is appreciated
EDIT: I have since fixed the SIGABRT but I can't seem to display any information into the UIScrollView. Anyone see any glaring issues in the code now?
#IBOutlet weak var infoView: UIView!
#IBOutlet weak var label: UILabel!
#IBOutlet weak var labelScroll: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
parseGame()
}
func parseGame() {
let url: URL = URL(string: "https://www.giantbomb.com/api/games/?api_key=5094689370c2cf4ae42a2a268af0595badb1fea8&format=json&field_list=name")!
print(url)
let responseData: Data? = try? Data(contentsOf: url)
if let responseData = responseData {
let json: Any? = try? JSONSerialization.jsonObject(with: responseData, options: [])
print(json ?? "Couldn't get JSON")
if let json = json {
let dictionary: [String: Any]? = json as? [String: Any]
if let dictionary = dictionary {
guard let result = dictionary["results"] as? [String:Any]? else { return }
if let result = result {
let name = result["name"] as? String?
if let name = name {
for i in 1...100 {
labelScroll.text = "Name: \(name)"
}
}
}
}
}
}
}
Oh no! It's the json parsing pyramid of death!
A Neat Little Alternative
If you know the structure of the json you will be receiving, you can create some structs to model your data. Also Swift has a cool protocol called "Codable" which does a lot of heavy lifting in parsing json and converting it to an object you create in code.
Let's Model the Data!
We'll create 2 structs that conform to the Codable Protocol. The first will hold the entire response data
struct ApiGameReponse:Codable {
var error:String
var limit:Int
var offset:Int
var pages:Int
var totalResults:Int
var status:Int
var version:String
var games:[Game]
private enum CodingKeys:String, CodingKey {
//The enum's rawValue needs to match the json's fieldName exactly.
//That's why some of these have a different string assigned to them.
case error
case limit
case offset
case pages = "number_of_page_results"
case totalResults = "number_of_total_results"
case status = "status_code"
case version
case games = "results"
}
}
and the second struct to model our game object. (Which is a single string, yes I know very exciting...) Since this particular json data represents a game with only 1 name property we don't really need a whole struct, but if the json was different and say had a "gamePrice" and "genre" properties, a struct would be nicer to look at.
struct Game:Codable {
var name:String
}
Keepin it pretty
Idk about you, but I don't like to look at ugly code. To keep it clean, we're going to split the function you made into two pieces. It's always better to have a bunch of smaller readable/reusable functions that each complete a single task rather than 1 superSizeMe number 3 with a large coke.
Getting the Data
Piece 1: Get the data from the URL
func getJSONData(_ urlString: String) -> Data? {
guard
let url:URL = URL(string: urlString),
let jsonData:Data = try? Data(contentsOf: url)
else { return nil }
return jsonData
}
Piece 2: Decode the data into something we can use
func getGameNames() -> [String] {
let apiEndpoint = "https://www.giantbomb.com/api/games/?api_key=5094689370c2cf4ae42a2a268af0595badb1fea8&format=json&field_list=name"
guard
let data = getJSONData(apiEndpoint),
let response = try? JSONDecoder().decode(ApiGameReponse.self, from: data)
else { return [] }
//Neat little function "map" allows us to create an array names from the array of game objects.
let nameArray = response.games.map { $0.name }
return nameArray
}
Implementation
And finally we can get the names and use them as needed.
override func viewDidLoad() {
//I recommend putting this somewhere else
//Perhaps create a method called "setup()" and call it in this class's inializer.
let names = getGameNames()
print(names) //Implement the array of names as needed
}
So I have a JSON file that I need to parse and update labels and image. In my storyboard I have 4 labels (image of an animal, region, it's weight and length) and uiimage where I need to put it's picture.
I need to update labels and image by parsing JSON.
This is how far I was able to get to...
My JSON look like this:
"data":[
{
"name":"Lion",
"thumbnail":"https://kopelion.org/wp-content/uploads/2016/10/Kimani.jpg",
"region":"Africa",
"stats":{
"max_weight":180,
"length":250
}
}
]
I tried to get into this by writing:
override func viewDidLoad() {
super.viewDidLoad()
guard let path = Bundle.main.path(forResource: "data", ofType: "json") else { return }
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
print(json)
guard let array = json as? [Any] else { return }
for animal in array {
guard let animalDict = animal as? [String: Any] else { return }
guard let animalName = animalDict["name"] as? String else { return }
guard let animalRegion = animalDict["region"] as? String else { return }
guard let animalStats = animalDict["stats"] as? String else { return }
print(animalName)
print(animalRegion)
print(animalStats)
}
} catch {
print(error)
}
}
First thing first, most of the times the main culprit is json file itself, as in your case.
You need to fix the json code first, by adding "{" at the top and "}" at the bottom of your json file's code.
This will make it valid json
And then do the following
Replace your code lines:
// 1
guard let array = json as? [Any] else { return }
// 2
guard let animalStats = animalDict["stats"] as? String else { return }
with this:
// 1
guard let dictionary = json as? [String:[Any]] else { return }
guard let array = dictionary["data"] else { return }
//2
guard let animalRegion = animalDict["region"] as? String else { return }
Quik tip : 1. Check your json using online tools like https://codebeautify.org/jsonviewer
Try to use JSONDecoder and JSONEncoder instead of JSONSerialization
You'll need to have properties in your class for the storyboard items you want to set. e.g.,:
#IBOutlet weak var name: UILabel! // outside functions but inside class, and hooked up in Storyboard
// inside viewDidLoad
name.text = animalDict["name"] as? String
I wouldn't bother with the intermediate variables unless you really need them.
As you are responsible for the JSON delete the enclosing dictionary data, it's not needed.
[{
"name":"Lion",
"thumbnail":"https://kopelion.org/wp-content/uploads/2016/10/Kimani.jpg",
"region":"Africa",
"stats":{
"max_weight":180,
"length":250
}
}]
Create two structs
struct Animal: Decodable {
let name: String
let thumbnail: URL
let region: String
let stats: Stats
}
struct Stats: Decodable {
let maxWeight, length: Int
}
In the view controller declare a data source array
var animals = [Animal]()
In viewDidLoad parse the data with JSONDecoder and assign the result to the data source array
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "data", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.animals = try! decoder.decode([Animal].self, from: data)
}
All guards and trys are actually not needed. The file must exist at compile time and cannot be modified at runtime so the code must not crash.
You can get the animal properties with
let animal = animals[0]
let name = animal.name
let weight = animal.stats.weight
print(name, weight)
Assuming there are more animals in the JSON use a loop
for animal in animals {
let name = animal.name
let weight = animal.stats.weight
print(name, weight)
}
How to update the labels is unclear because there is no significant information about the design in your question.
To get the image load it asynchronously with URLSession
I am working with Swift on Xcode and I try to parse a JSON file to retrieve some data about nearby stores.
My source code is the following:
import GooglePlaces
import SwiftyJSON
class Place {
let name: String
let coordinates: CLLocationCoordinate2D
init(diction:[String : Any])
{
let json = JSON(diction)
name = json["name"].stringValue //as! String
let lat = json["geometry"]["location"]["lat"].doubleValue as CLLocationDegrees
let long = json["geometry"]["location"]["lng"].doubleValue as CLLocationDegrees
coordinates = CLLocationCoordinate2DMake(lat, long)
}
}
class ViewController: UIViewController, MKMapViewDelegate, SceneLocationViewDelegate {
var urlString = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?"
urlString += "&location=51.507514,-0.073603"
urlString += "&radius=1500" //meters
urlString += "&name=Specsavers"
urlString += "&key=**************************"
guard let url = URL(string: urlString) else {return}
var places = [Place]()
var request = URLRequest(url:url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
print("HEREurlSession")
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(json) // json results are printed fine here
if let results = json["results"] as? [[String : Any]] {
for place in results {
places.append(Place(diction: place))
}
}
else {
print("return")
}
}
catch{
}
}
}
task.resume()
let size = places.count
print("HERE: ", size)
}
The build is successful but the output is size = 0 which means that I do not retrieve the data and the variable places is empty.
I do not know if it is exactly relevant but I get the following warning: Cast from 'MDLMaterialProperty?!' to unrelated type '[[String : Any]]' always fails for the line if let results = json["results"] as? [[String : Any]] in my source code.
Why I do not parse the JSON file correctly and I do not retrieve the data the I want to?
URLSession.shared.dataTask(with:) is asynchronous. This means, it runs in the background. You are executing
let size = places.count
print("HERE: ", size)
while the dataTask is still working.
Instead, you should use your result in the completion handler:
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in
print("HEREurlSession")
if let content = data {
do {
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(json)
if let results = json["results"] as? [[String : Any]] {
for place in results {
places.append(Place(diction: place))
}
}
else {
print("return")
}
}
catch{
}
}
// Use your result here
let size = places.count
useResultSize(size)
}
task.resume()
func useResultSize(_ size: Int) {
// Use your result here
print("HERE: ", size)
}
UPDATE
It seems, that you are missing what asynchronous execution actually means. Let me try to explain.
Lets mark the execution order in the code:
First, the red parts of your code are executed. Program execution starts at the top, then moves to the bottom red box and only after that (once the network request is finished) the green part is executed.
That means, that you can only use the result of the network request in the green part of the code. Outside of the green part, the result is not guaranteed to be available.
If you follow my initial advice, than everything should work. Please see the successful execution in my playground:
I have a JSON fetch happening and then I do stuff with the data. My JSONObject is created and then I go about working with the data. A sample can be seen here: https://openlibrary.org/api/books?bibkeys=1593243987&f&jscmd=data&format=json
My first block to extract the author name is working perfectly, however the second to extract the cover url as a string isn't even running and I have no idea why.
If I set a breakpoint at if let thumbs = bookDictionary["cover"] as? NSArray {it stops, but then when I 'step through' the code, it jumps to the end and moves on, not even running anything inside the block.
I would appreciate any help anyone can offer. I'm using Swift 2.0 / Xcode 7b6.
let requestURL = ("https://openlibrary.org/api/books?bibkeys=" + lookUpID + "&f&jscmd=data&format=json")
let url = NSURL(string: requestURL)
let req = NSURLRequest(URL: url!)
let dataTask = session.dataTaskWithRequest(req) {
(data, response, error) in
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary
if let bookDictionary: AnyObject = jsonObject!["\(self.lookUpID)"] {
// Retrieve the author name
var names = [String]()
if let authors = bookDictionary["authors"] as? NSArray {
for author in authors {
if let author = author as? NSDictionary,
let name = author["name"] as? String {
names.append(name)
}
}
}
// Retrieve cover url
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSArray {
// This code isn't running at all.
for thumb in thumbs {
if let thumb = thumb as? NSDictionary,
let thumbnail = thumb["medium"] as? String {
coverThumbURL = thumbnail
}
}
}
}
Thanks for the help. I did some looking around & fixed the casting.
var coverThumbURL: String = ""
if let thumbs = bookDictionary["cover"] as? NSDictionary {
let thumbnail = thumbs.valueForKey("medium") as? String
coverThumbURL = thumbnail!
}