Download nested JSON from URL and make Observable Object SwiftUI, ERROR: "Missing Argument for parameter 'from' in call" - json

I am trying to download a nested JSON file from an URL. I want to make it Observable Object so that my App will update its view when the download is finished.
Here I have my structures and the class I have created in order to make it an Observable Object. This is also where my Error: "Missing argument in parameter 'from' in call" appears: (Thanks for any help!)
struct SpotDetail: Codable {
var spot: String
var day0: DayDetail
var day1: DayDetail
var day2: DayDetail
enum CodingKeys: String, CodingKey {
case spot = "spot"
case day0 = "day0"
case day1 = "day1"
case day2 = "day2"
}
}
struct DayDetail: Codable {
var date: String
var wind: [Int]
var gust: [Int]
enum CodingKeys: String, CodingKey {
case date = "date"
case wind = "wind"
case gust = "gust"
}
}
class networkSpotData: ObservableObject {
let objectWillChange = ObjectWillChangePublisher()
var WindSpot = SpotDetail() { // ERROR: "Missing Argument for parameter 'from' in call"
willSet {
self.objectWillChange.send()
}
}
}
And the function that downloads from the URL:
extension ContentView {
func loadspotdata(url: String) {
guard let url = URL(string: "https://raw.githubusercontent.com/Bene2907/Bene2907.github.io/main/template.json") else {
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response_obj = try? JSONDecoder().decode(SpotDetail.self, from: data) {
DispatchQueue.main.async {
spotData.WindSpot = response_obj
}
}
}
}.resume()
}
}
Finally I have my view to present the data:
struct ContentView: View {
#ObservedObject var spotData = networkSpotData()
var body: some View {
VStack {
Text("\(spotData.WindSpot.spot)")
.padding()
Text("\(spotData.WindSpot.day0.date)")
.padding()
}.onAppear(perform: {
loadspotdata()
})
}
}

To use default constructor in
var WindSpot = SpotDetail() { // ERROR: "Missing Argument for parameter 'from' in call"
you have to initialize properties with default values in SpotDetail, like
struct SpotDetail: Codable {
var spot: String = ""
var day0: DayDetail = DayDetail() // this also should be initialised properties
...
}

Related

How to read and display a dictionary from JSON?

I am working on an app that fetches the data from JSON and displays it.
However, I am stuck with an error saying Instance method 'appendInterpolation(_:formatter:)' requires that '[String : Int]' inherit from 'NSObject'
Here is my data structure:
struct Data: Codable {
var message: String
var data: Objects
}
struct Objects: Codable {
var date: String
var day: Int
var resource: String
var stats, increase: [String: Int]
}
Function to fetch the data:
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(Data.self, from: data)
self.data = decodedData
} catch {
print("Hey there's an error: \(error.localizedDescription)")
}
}
}.resume()
}
And a ContentView with the #State property to pass the placeholder data:
struct ContentView: View {
#State var data = Data(message: "", data: Objects(date: "123", day: 123, resource: "", stats: ["123" : 1], increase: ["123" : 1]))
var body: some View {
VStack {
Button("refresh") { getData() }
Text("\(data.data.date)")
Text("\(data.data.day)")
Text(data.message)
Text("\(data.data.stats)") //error is here
Here is an example of JSON response
I wonder if the problem is in data structure, because both
Text("\(data.data.date)")
Text("\(data.data.day)")
are working just fine. If there are any workarounds with this issue – please, I would highly appreciate your help!:)
stats is [String: Int], and so when you want to use it, you need to supply the key to get the value Int, the result is an optional that you must unwrap or supply a default value in Text
So use this:
Text("\(data.data.stats["123"] ?? 0)")
And as mentioned in the comments, do not use Data for your struct name.
EDIT-1: there are two ways you can make the struct fields camelCase; one is using the CodingKeys as shown in ItemModel, or at the decoding stage, as shown in the getData() function. Note, I've also updated your models to make them easier to use.
struct DataModel: Codable {
var message: String
var data: ObjectModel
}
struct ObjectModel: Codable {
var date: String
var day: Int
var resource: String
var stats: ItemModel
var increase: ItemModel
}
struct ItemModel: Codable {
var personnelUnits: Int
var tanks: Int
var armouredFightingVehicles: Int
// ...
// manual CodingKeys
// enum CodingKeys: String, CodingKey {
// case tanks
// case personnelUnits = "personnel_units"
// case armouredFightingVehicles = "armoured_fighting_vehicles"
// }
}
struct ContentView: View {
#State var dataModel = DataModel(message: "", data: ObjectModel(date: "123", day: 123, resource: "", stats: ItemModel(personnelUnits: 123, tanks: 456, armouredFightingVehicles: 789), increase: ItemModel(personnelUnits: 3, tanks: 4, armouredFightingVehicles: 5)))
var body: some View {
VStack {
Button("get data from Server") { getData() }
Text("\(dataModel.data.date)")
Text("\(dataModel.data.day)")
Text(dataModel.message)
Text("\(dataModel.data.stats.armouredFightingVehicles)") // <-- here
}
}
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // <-- here
dataModel = try decoder.decode(DataModel.self, from: data)
} catch {
print("--> error: \(error)")
}
}
}.resume()
}
}
}

Executing function after data loads into view SwiftUI

I want the Snapshot function to be executed once my ContentView has loaded in all the network data. I have no problem with the API call, I just simply want the Snapshot function to run after all data has loaded into ContentView.
Expected result after pressing button:
Expected result after pressing button
Actual result after pressing button:
Actual result after pressing button
ContentView.swift
extension View {
func Snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}
struct ContentView: View {
#ObservedObject var FetchResults = fetchResults()
var contentView: some View {
ContentView()
}
var body: some View {
Group {
if FetchResults.dataHasLoaded {
VStack {
Text(FetchResults.clout.postFound?.body ?? "n/a")
.padding()
.background(Color.blue)
.mask(RoundedRectangle(cornerRadius: 30, style: .continuous))
.shadow(color: Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)).opacity(0.3), radius: 10, x: 0, y:10)
.padding()
.frame(maxWidth: 300)
}
Button(action: {
let image = contentView.Snapshot()
print(image)
}, label:{Text("press me to print view")})
} else {
Text("loading data")
}
}
}
}
GotPost.swift -- my ViewModel running the API call
class fetchResults: ObservableObject {
#Published var clout = Cloutington()
#Published var dataHasLoaded = false
init() {
getData { clout in
self.clout = clout
}
}
private func getData(completion: #escaping (Cloutington) -> ()) {
let parameters = "{\r\n \"PostHashHex\": \"2f9633c2460f23d9d5690fe9fd058457ebeb01f6f75805aa426cdaab90a1f4b4\"\r\n}"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: "https://bitclout.com/api/v0/get-single-post")!,timeoutInterval: Double.infinity)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = postData
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { (responseData, response, error) in
print(error)
print(response)
print(responseData)
if let resData = responseData {
let decoder = JSONDecoder()
do
{
let finalData = try decoder.decode(Cloutington.self, from: resData)
DispatchQueue.main.async {
completion(finalData)
self.dataHasLoaded = true
}
}
catch (let error)
{
print(error)
}
}
}
task.resume()
}
}
PostFound.swift -- model parsing JSON data
struct Cloutington: Decodable {
var postFound: PostFound?
enum CodingKeys: String, CodingKey {
case postFound = "PostFound"
}
}
struct PostFound: Decodable {
var id: String?
var postHashHex, posterPublicKeyBase58Check, parentStakeID, body: String?
var imageURLs: [String]?
// var recloutedPostEntryResponse: JSONNull?
var creatorBasisPoints, stakeMultipleBasisPoints: Int?
var timestampNanos: Double?
var isHidden: Bool?
var confirmationBlockHeight: Int?
var inMempool: Bool?
var profileEntryResponse: ProfileEntryResponse?
var likeCount, diamondCount: Int?
var isPinned: Bool?
var commentCount, recloutCount: Int?
var diamondsFromSender: Int?
enum CodingKeys: String, CodingKey {
case postHashHex = "PostHashHex"
case posterPublicKeyBase58Check = "PosterPublicKeyBase58Check"
case parentStakeID = "ParentStakeID"
case body = "Body"
case imageURLs = "ImageURLs"
case creatorBasisPoints = "CreatorBasisPoints"
case stakeMultipleBasisPoints = "StakeMultipleBasisPoints"
case timestampNanos = "TimestampNanos"
case isHidden = "IsHidden"
case confirmationBlockHeight = "ConfirmationBlockHeight"
case inMempool = "InMempool"
case profileEntryResponse = "ProfileEntryResponse"
case likeCount = "LikeCount"
case diamondCount = "DiamondCount"
case isPinned = "IsPinned"
case commentCount = "CommentCount"
case recloutCount = "RecloutCount"
case diamondsFromSender = "DiamondsFromSender"
}
}
// MARK: - ProfileEntryResponse
struct ProfileEntryResponse: Decodable {
var publicKeyBase58Check, username, profileEntryResponseDescription, profilePic: String?
var isHidden, isReserved, isVerified: Bool?
var coinPriceBitCloutNanos, stakeMultipleBasisPoints: Int?
enum CodingKeys: String, CodingKey {
case publicKeyBase58Check = "PublicKeyBase58Check"
case username = "Username"
case profileEntryResponseDescription = "Description"
case profilePic = "ProfilePic"
case isHidden = "IsHidden"
case isReserved = "IsReserved"
case isVerified = "IsVerified"
case coinPriceBitCloutNanos = "CoinPriceBitCloutNanos"
case stakeMultipleBasisPoints = "StakeMultipleBasisPoints"
}
} ```
Really appreciate the help 🙏
You're going to get more and better help in general if you follow the Swift API Design Guidelines for naming types and variables. That means using lower case for the initial letters of properties and methods, and upper case for the initial letters of types. I understand that you may come from a platform like C# where the conventions are different. But when you don't do things the Swift way (or worse, as in your code, where you do both!), you make it harder for us to understand your code.
Also, this is a bad pattern:
#ObservedObject var FetchResults = fetchResults()
The problem is that SwiftUI doesn't understand the ownership of the FetchResults object, and will recreate it each time the view is recreated. In this case, you should use #StateObject instead:
#StateObject var fetchResults = FetchResults()
Check out this article by Paul Hudson for guidance on when to use #StateObject and when to use #ObservableObject.
However, that won't fix the problem you posted about. Your code creates the snapshot by saying contentView.Snapshot(), which calls your contentView computed property, which creates a new ContentView from scratch, which creates a new, unloaded FetchResults (regardless of whether you use #ObservedObject or #StateObject).
You need to reuse the FetchResults that has already loaded its data, so just say self.snapshot() instead:
Button(
action: {
let image = self.snapshot()
print(image)
},
label: {
Text("press me to print view")
}
)

SwiftUI macOS JSON convert in class Codable

I get the following json from an api call but am having trouble making the proper structure in swift and then getting the data as an array.
JSON:
{
"status":"ok",
"users":[
{
"position":0,
"user":{
"pk":"",
"full_name":"",
"username":"",
"profile_pic_url":""
}
},...
]
}
Swift:
class Response: Codable {
var status: String
var users: [User]?
}
class User: Identifiable, Codable {
var uuid = UUID()
var pk: String
var full_name: String
var username: String
var profile_pic_url: String
enum CodingKeys: String, CodingKey {
case
pk = "user.pk",
full_name = "user.full_name",
username = "user.username",
profile_pic_url = "user.profile_pic_url"
}
}
class Fetch: ObservableObject {
#Published var results = [User]()
#Published var resultState = false
#Published var errorState = false
init(url: String) {
self.results = []
let url = URL(string: url)!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
self.results = results.users ?? []
self.resultState = true
}
print("Widget: Ok.")
} else {
self.results = []
self.resultState = true
print("Widget: No data.")
}
} catch {
self.errorState = true
self.resultState = true
print("Widget: Error", error)
}
}.resume()
}
}
Code:
#ObservedObject var fetch = Fetch(url: "")
List(fetch.results) { user in
UserItem(user: user)
}
The problem is that inside array users, it contains an object, this object contains two elements a position attribute and then the user object.
What I think I'm doing wrong is taking the user object.
Can anyone help me out?
Edit:
struct Response: Codable {
let status: String
let users: [UserType]?
}
struct UserType: Codable {
let position: Int
let user: User
}
struct User: Codable {
let pk: String
let full_name: String
let username: String
let profile_pic_url: String
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
}
class Fetch: ObservableObject {
#Published var results = [User]()
#Published var resultState = false
#Published var errorState = false
init(url: String) {
self.results = []
let url = URL(string: url)!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode(Response.self, from: data)
let users = results.users?.map { $0.user }
DispatchQueue.main.async {
self.results = users ?? []
self.resultState = true
}
print("Widget: Ok.")
} else {
self.results = []
self.resultState = true
print("Widget: No data.")
}
} catch {
self.errorState = true
self.resultState = true
print("Widget: Error", error)
}
}.resume()
}
}
List(fetch.results) { user in
UserItem(user: user)
}
You can try this.
struct Response: Codable {
let status: String
let users: [UserWPosition]
var userNoPositions: [UserInfo] { // computed value with only array of userinfo
users.compactMap { $0.user }
}
}
// MARK: - User with position object
struct UserWPosition: Codable {
let position: Int // commenting this will also do no effect
let user: UserInfo
}
// MARK: - UserInfo
struct UserInfo: Codable {
let pk, fullName, username, profilePicURL: String
enum CodingKeys: String, CodingKey {
case pk
case fullName = "full_name"
case username
case profilePicURL = "profile_pic_url"
}
}
Read the comments I added to the code decoding will not code a key that's not added to the struct so commenting out position will have no issue also, the hierarchy of it should be like this now I added a userNoPositions computed value in response to give array of users easily .
Simply to access the array without positions
var resp = try! JSONDecoder().decode(Response.self, from: encoded) // encoded is the data from json
print(resp.userNoPositions) // the array
You need an extra struct that holds the User type
struct UserType: Codable {
let position: Int
let user: User
}
Meaning the top type becomes
struct Response: Codable {
let status: String
let users: [UserType]?
}
You also need to change the CodingKeys enum since it should just contain the property names which mean the enum can be written as
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
For completeness here is the full User type
struct User: Identifiable, Codable {
var uuid = UUID()
var pk: String
var full_name: String
var username: String
var profile_pic_url: String
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
}
and when decoding then you can extract the users array with the map function
do {
let results = try JSONDecoder().decode(Response.self, from: data)
let users = results.users?.map { $0.user }
....
Note that I changed from class to struct because struct is better suited for this but class works as well. I also wonder why the users property is optional, I didn't change that but can the array really be nil?

Stored result of json decode is suddenly gone/ Observed Object doesn't react in WatchOS

I decode json data from an iPhone to a watch and store it into an object (custom class: PeopleObj) which is declared as #ObservableObject. However, the contenview containing this object doesn't get the data
This is the data model:
struct Person: Codable {
var pid : UUID
var dept: String
var name: String
init(pid: UUID, dept: String, name: String){
self.pid = pid
self.dept = dept
self.name = name
}
}
class PeopleObj: ObservableObject, Identifiable , Codable{
#Published var people: [Person] = []
// get codable
init() { }
enum CodingKeys: CodingKey {
case people
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(people, forKey: .people)
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
people = try container.decode(Array.self, forKey: .people)
}
}
Following is the decode part at WatchOS, here I get the json data as a reply on a request and decode it into the peopleObj:
let jsonEncoder = JSONEncoder()
do {
let jsonDataRequest = try jsonEncoder.encode(request)
print("send request")
self.session?.sendMessageData(jsonDataRequest, replyHandler: { response in
print(">>>>>>>>>>>>>>>>> DATA-Reply vom iPhone received: \(response)")
DispatchQueue.main.async {
let jsonDecoder = JSONDecoder()
do {
self.peopleObj = try jsonDecoder.decode(PeopleObj.self, from: response)
for person in self.peopleObj.people {
print("received person: \(person.name), dept \(person.dept)")
// here it prints correctly
}
} // End do decode
catch { print("decode catch!!!!!!!") }
} // Dispatch main
},
errorHandler: { error in
print("Error sending message: %#", error)
}) // sendMessageData
} // End do decode
catch { print("encode catch!!!!!!!") }
This is the contenview, observing the object:
struct ContentView: View {
#ObservedObject var peopleObj: PeopleObj
var body: some View {
VStack {
List(peopleObj.people, id: \.pid) { person in
HStack {
Text("name: \(person.name)")
Spacer()
Text("dept: \(person.dept)")
}
}
}
}
}
I tried decoding the json into a struct and then manualy add the values to the peopleObj - that works but doesn't seem to be the correct process to me! I can't explain why, guess it has something to do with value vs referencing.
Any help and or idea is more than welcome!!!!!
Edit: The peopleObj is defined in the HostingController:
class HostingController: WKHostingController<ContentView> , WCSessionDelegate{
#ObservedObject var peopleObj:PeopleObj = PeopleObj()
The ContentView is called from the HostingController
override var body: ContentView {
return ContentView(peopleObj: peopleObj)
}

SwiftUI - JSON from GitHub REST API fails to parse

I am attempting to create an app in SwiftUI using GitHub's REST API. I am attempting to only retrieve only the login name and profile picture of a given user's followers and then populate a List.
FollowersView.swift
import SwiftUI
struct Follower: Codable, Hashable {
public var login: String
public var avatar_url: String
}
struct Response: Codable {
var followers: [Follower] = [Follower]()
}
struct FollowersView: View {
#EnvironmentObject var followerInfo: FollowerInfo
#State var followers: [Follower] = [Follower]()
init() {
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
}
var body: some View {
NavigationView {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
List(followers, id: \.self) { follower in
HStack(spacing: 10) {
Image(decorative: "\(follower.avatar_url).png")
.resizable()
.frame(width: 75, height: 75)
HStack(spacing: 5) {
Image(systemName: "person")
Text("\(follower.login)").fontWeight(.heavy)
}
}
}
.onAppear(perform: loadData)
}
.navigationBarTitle("\(followerInfo.followerUsername)")
}
}
func loadData() {
guard let url = URL(string: "https://api.github.com/users/\(followerInfo.followerUsername)/followers") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.followers = decodedResponse.followers
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown Error")")
}.resume()
}
}
struct FollowersView_Previews: PreviewProvider {
static var previews: some View {
FollowersView()
}
}
The code gets to print("Fetch failed: \(error?.localizedDescription ?? "Unknown Error")") and prints "Unknown Error" before crashing. Upon inspecting the data that comes back, it comes up with nothing, at least not that I can tell anyway. (I'm not entirely accustomed to the Xcode debugger). I double checked the API response in a browser and the fields in the response in the browser and the names of the properties in the Codable struct match. So, I'm not exactly sure what's going on here. Any suggestions on how I can fix this issue?
If I understood your problem correctly. Update your Codable as shown below:
import Foundation
// MARK: - FollowerElement
struct FollowerElement: Codable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
let url, htmlURL, followersURL: String
let followingURL, gistsURL, starredURL: String
let subscriptionsURL, organizationsURL, reposURL: String
let eventsURL: String
let receivedEventsURL: String
let type: String
let siteAdmin: Bool
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case htmlURL = "html_url"
case followersURL = "followers_url"
case followingURL = "following_url"
case gistsURL = "gists_url"
case starredURL = "starred_url"
case subscriptionsURL = "subscriptions_url"
case organizationsURL = "organizations_url"
case reposURL = "repos_url"
case eventsURL = "events_url"
case receivedEventsURL = "received_events_url"
case type
case siteAdmin = "site_admin"
}
}
typealias Follower = [FollowerElement]
You can remove properties you don't need from above code.
Now update your dataTask method
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Fetch failed: \(error.localizedDescription)")
} else {
if let data = data {
do {
let follower = try JSONDecoder().decode(Follower.self, from: data)
DispatchQueue.main.async {
print(follower.count)
}
} catch {
print(error)
}
}
}
}.resume()