SWIFTUI Displaying JSON Data in ContentView - swiftui

I have been having trouble displaying my JSON into my content view. I can decode the data and save it into a dictionary as I have printed and seen. However when its time to display it in ContentView with a ForEach. I'm getting this error Cannot convert value of type '[String : String]' to expected argument type 'Binding' Below is my code for my ContentView, Struct and ApiCall. I have read other solutions on stackoverflow and tried them all but they do not work.
struct ContentView: View {
#StateObject var api = APICALL()
var body: some View {
let country = api.storedData.countries
VStack(alignment: .leading) {
ForEach(country.id, id: \.self) { country in
HStack(alignment: .top) {
Text("\(country.countries)")
}
}
.onAppear {
api.loadData()
}
}
}
}
My ApiCall class which loads the data, as well as the struct.
// MARK: - Country
struct Country: Codable, Identifiable {
let id = UUID()
var countries: [String: String]
enum CodingKeys: String, CodingKey {
case countries = "countries"
}
}
class APICALL: ObservableObject {
#Published var storedData = Country(countries: [:])
func loadData() {
let apikey = ""
guard let url = URL(string:"https://countries-cities.p.rapidapi.com/location/country/list?rapidapi-key=\(apikey)") else {
print("Your Api end point is Invalid")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let response = try? JSONDecoder().decode(Country.self, from: data) {
DispatchQueue.main.async {
self.storedData.countries = response.countries
print(self.storedData.countries)
}
return
}
}
}
.resume()
}
}
Any Point in the right direction would be absolutely helpful.

you could try this approach to display your countries data:
struct ContentView: View {
#StateObject var api = APICALL()
var body: some View {
VStack(alignment: .leading) {
// -- here --
ForEach(Array(api.storedData.countries.enumerated()), id: \.0) { index, country in
HStack(alignment: .top) {
Text("\(country.key) \(country.value)")
}
}
.onAppear {
api.loadData()
}
}
}
}
you can also use this, if you prefer:
ForEach(api.storedData.countries.sorted(by: >), id: \.key) { key, value in
HStack(alignment: .top) {
Text("\(key) \(value)")
}
}

Related

SwiftUI Object details on list click

Building a crystal app. Displaying a list, showing details on click.
Been looking into ObservableObject, Binding, etc.
Tried #State in CrystalView but got lost pretty quickly.
What's the easiest way to pass data around views? Watched a few videos, still confused.
How do I pass crystals[key] into CrystalView()?
struct ContentView: View {
#State private var crystals = [String:Crystal]()
var body: some View {
Text("Crystals").font(.largeTitle)
NavigationView {
List {
ForEach(Array(crystals.keys), id:\.self) { key in
HStack {
NavigationLink(destination: CrystalView()) {
Text(key)
}
}
}
}.onAppear(perform:loadData)
}
}
func loadData() {
guard let url = URL(string: "https://lit-castle-74820.herokuapp.com/api/crystals") else { return }
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data else { return }
do {
let decoded = try JSONDecoder().decode([String:Crystal].self, from: data)
DispatchQueue.main.async {
print(decoded)
self.crystals = decoded
}
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
}.resume()
}
}
struct Crystal: Codable, Identifiable {
var id = UUID()
let composition, formation, colour: String
let metaphysical: [String]
}
struct CrystalView: View {
var body: some View {
Text("crystal")
}
}
try this approach, works well for me:
struct Crystal: Codable, Identifiable {
var id = UUID()
let composition, formation, colour: String
let metaphysical: [String]
// -- here, no `id`
enum CodingKeys: String, CodingKey {
case composition,formation,colour,metaphysical
}
}
struct CrystalView: View {
#State var crystal: Crystal? // <-- here
var body: some View {
Text("\(crystal?.composition ?? "no data")")
}
}
struct ContentView: View {
#State private var crystals = [String:Crystal]()
var body: some View {
Text("Crystals").font(.largeTitle)
NavigationView {
List {
ForEach(Array(crystals.keys), id:\.self) { key in
HStack {
NavigationLink(destination: CrystalView(crystal: crystals[key])) { // <-- here
Text(key)
}
}
}
}.onAppear(perform: loadData)
}
}
func loadData() {
guard let url = URL(string: "https://lit-castle-74820.herokuapp.com/api/crystals") else { return }
URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data else { return }
do {
let decoded = try JSONDecoder().decode([String:Crystal].self, from: data)
DispatchQueue.main.async {
print("decoded: \(decoded)")
self.crystals = decoded
}
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
}.resume()
}
}
Something like this:
struct ContentView: View {
#State private var crystals: [Crystal] = []
var body: some View {
NavigationView {
List {
ForEach(crystals) { crystal in
NavigationLink(destination: CrystalView(crystal: crystal)) {
Text(crystal.name)
}
}
}
.navigationTitle("Crystals")
// initial detail view
Text("Select a crystal")
}
.task {
crystals = try? await fetchCrystals()
}
}
func fetchCrystals() async throws -> [Crystal] {
let (data, _) = try await URLSession.shared.data(from: "https://lit-castle-74820.herokuapp.com/api/crystals")
let decoder = JSONDecoder()
return try decoder.decode([Crystal].self, from: data) // you might want to convert this downloaded struct into a more suitable struct for the app.
}
}
struct CrystalView: View {
let crystal: Crystal
var body: some View {
Text(crystal.composition)
.navigationTitle(crystal.name)
}
}

Swiftui Mapping Nested JSON to Flat Model

I need to access the data in the nested dictionary of the Memodel struct. From both the music and image dictionary. Please any help is needed to map out correctly, i have tried using AzampSharp's example https://www.youtube.com/watch?v=b5wVIQNrI6k but i believe i am doing something wrong. Thanks.
import SwiftUI
struct MemodelAPIResult: Codable {
let data: [Memodel]
enum CodingKeys: String, CodingKey {
case data = "results"
}
}
struct Memodel: Identifiable, Codable {
var id: String
var followers: String
var following: String
let music: [MemodelMusic]
let images: [MemodelImages]
}
struct MemodelMusic: Identifiable, Codable, Hashable {
var id: String
var musicfile: URL
var musicname: String
var musicartistname: String
var musicgenre: String
}
struct MemodelImages: Identifiable, Codable, Hashable {
var id: String
var albumimages: URL
var abumlikes: String
var albumviews: String
}
Below is my ObservableObject in my View Model
import Foundation
import SwiftUI
import Combine
import CryptoKit
class MeViewmodel: ObservableObject {
#Published var me: [Memodel]? = nil
init() {
self.fetchme()
}
func fetchme() {
let url = ""
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if let error = err{
print(error.localizedDescription)
return
}
guard let APIData = data else {
print("No Data found")
return
}
do {
let new = try JSONDecoder().decode(MemodelAPIResult.self, from: APIData)
DispatchQueue.main.async {
self.me = new.data
}
}
catch{
print(error)
}
}
.resume()
}
}
And then the item view
struct MeMusicItemView: View {
//E-MARK: - Properties
var me: Memodel
//E-MARK: - Body
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(me.music[0].musicname)
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.white)
Text(me.music[0].musicartistname)
.font(.caption2)
.fontWeight(.light)
.foregroundColor(.white)
Text(me.music[0].musicgenre)
.font(.system(size: 8))
.fontWeight(.light)
.foregroundColor(.gray)
}
}
}
}
And also the ForEach in the parent View....
if let meMusicData = meMusicData.mememe {
ForEach(meMusicData) { music in
MeMusicItemView(memusic: music)
}
} else {
ProgressView()
.padding(.top, 20)
}
There is not enough info for me to really understand what you are doing, but
here is some code you can have a look at and recycle for your purpose:
struct ContentView: View {
#StateObject var viewModel = MeViewmodel() // <-- here your model
var body: some View {
List {
ForEach(viewModel.me) { memod in // <-- loop over the Memodel array
ForEach(memod.music) { music in // <-- loop over the MemodelMusic array
MeMusicItemView(memusic: music) // <-- display 1 MemodelMusic
}
}
}
}
}
struct MeMusicItemView: View {
//E-MARK: - Properties
#State var memusic: MemodelMusic // <-- here
//E-MARK: - Body
var body: some View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(memusic.musicname)
.font(.callout)
.fontWeight(.medium)
.foregroundColor(.pink)
Text(memusic.musicartistname)
.font(.caption2)
.fontWeight(.light)
.foregroundColor(.green)
Text(memusic.musicgenre)
.font(.system(size: 8))
.fontWeight(.light)
.foregroundColor(.blue)
}
}
}
}
class MeViewmodel: ObservableObject {
#Published var me: [Memodel] = [] // <--- here no optional, it is easier to deal with
init() {
self.fetchme()
}
func fetchme() {
// ......
}
}
struct Memodel: Identifiable, Codable {
var id: String
var followers: String
var following: String
let music: [MemodelMusic]
let images: [MemodelImages]
}
struct MemodelMusic: Identifiable, Codable, Hashable {
var id: String
var musicfile: URL
var musicname: String
var musicartistname: String
var musicgenre: String
}
struct MemodelImages: Identifiable, Codable, Hashable {
var id: String
var albumimages: URL
var abumlikes: String
var albumviews: String
}

How to loop through API call data and display in view

I know I can use for each for this, but every time I try to implement according to documentation it throws some kind of error regarding syntax.
Here is my view:
import SwiftUI
import Combine
struct HomeTab: View {
#StateObject var callDevices = CallDevices()
var body: some View {
NavigationView {
devices
.onAppear {
callDevices.getDevices()
}
}
}
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(content: callDevices.getDevices(), id: \.self) { device in
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
}
struct HomeTab_Previews: PreviewProvider {
static var previews: some View {
HomeTab()
}
}
Here is my Call Devices which works without issue in other views:
class CallDevices: ObservableObject {
private var project_id: String = "r32fddsf"
#Published var devices = [Device]()
func getDevices() {
guard let url = URL(string: "www.example.com") else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Authorization")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else {print(error!.localizedDescription); return }
// guard let data = data else {print("empty data"); return }
let theData = try! JSONDecoder().decode(Welcome.self, from: data!)
DispatchQueue.main.async {
self.devices = theData.devices
}
}
.resume()
}
}
is the issue in the way I am calling my function?
try this:
(you may need to make Device Hashable)
private var devices: some View {
VStack(alignment: .leading, spacing: nil) {
ForEach(callDevices.devices, id: \.self) { device in // <-- here
// i want to loop through and display here //
HStack{
Text(device.Name)
Text(device.Status)
}
}
Spacer()
}
}
If Device is Identifiable, you can remove the id: \.self.
struct Device: Identifiable, Hashable, Codable {
let id = UUID()
var Name = ""
var Status = ""
// ... any other stuff you have
}

Cannot get view to update with downloaded JSON

I am stuck at the simplest place right now.. I'm making a network request and just want the view to be updated once the JSON is received..
And I verified that:
JSON is valid
Valid response received (verified in print statement)
I've done this about 50 times and don't know why I'm stuck at this point.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
HStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await NetworkManager().getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
private let apiKey = ""//removed
private var baseUrlString: String {
"https://newsapi.org/v2/"
}
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: baseUrlString + "everything?q=\(searchItem)&apiKey=\(apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
DispatchQueue.main.async {
self.newsItem = news
self.articles = self.newsItem!.articles
}
}
}
struct News: Codable {
var totalResults: Int?
let articles: [Article]
}
struct Article: Codable, Hashable {
let author: String?
let title: String
let description: String
let url: String
let urlToImage: String?
let publishedAt: String
let content: String
}
Edit: removed apiKey
getAllArticles() is view-related, which means you should probably implement this function inside the View instead of ObservableObject.
struct ContentView: View {
#StateObject var nm = NetworkManager()
var body: some View {
VStack {
ScrollView {
VStack {
ForEach(nm.articles, id: \.hashValue) { article in
Text("Hello")
}
}.task {
do {
try await getAllArticles(for: "mario")
} catch { print(error) }
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
extension ContentView {
func getAllArticles(for searchItem: String) async throws {
let url = URL(string: nm.baseUrlString + "everything?q=\(searchItem)&apiKey=\(NetworkManager.apiKey)")!
let (data, _) = try await URLSession.shared.data(from: url)
let news = try JSONDecoder().decode(News.self, from: data)
// Not necessary
// DispatchQueue.main.async {
nm.newsItem = news
nm.articles = nm.newsItem!.articles
// }
}
}
final class NetworkManager: ObservableObject {
#Published var newsItem: News?
#Published var articles: [Article] = []
static let apiKey = "YOUR_API_KEY"
var baseUrlString: String {
"https://newsapi.org/v2/"
}
}

Pass Object details to another view [SwiftUI]

Facing problem to understand how to move Object details to another view using NavigationLink, I have read many articles and watched video, they all do the same as I do except for the Preview struct, they use local data and easily they map the view to the first item of the data like data[0]. While in my case, I fetch the data online, hence the above way did not help me to overcome the issue with the Preview struct, ERROR: Missing argument for parameter
Articles have been read:
developer.apple.com/tutorials/swiftui/building-lists-and-navigation
www.raywenderlich.com/5824937-swiftui-tutorial-navigation
www.youtube.com/watch?v=BCSP_uh0co0&ab_channel=azamsharp
/// Main View Code:
import SwiftUI
import SDWebImageSwiftUI
struct HomeView: View {
#State var posts: [Posts] = []
#State var intPageNo: Int = 0
var body: some View {
NavigationView {
List(posts) {post in
NavigationLink(destination: ViewPostView(post: post)){
VStack{
HStack{
WebImage(url: URL(string: post.featured_image))
.resizable()
.placeholder(Image("logo"))
.frame(width: 150, height: 150, alignment: .center)
VStack(alignment: .leading, spacing: 10.0){
Text("By: \(post.author_name)")
Text("Since: \(post.since)")
Text("City: \(post.city_name)")
Text("Category: \(post.category_name)")
}
.font(.footnote)
Spacer()
}
Text(post.title)
.font(.body)
.fontWeight(.bold)
.frame(alignment: .trailing)
.flipsForRightToLeftLayoutDirection(true)
}
}
}
.onAppear{
self.intPageNo += 1
ApiPosts().getPosts(intPage: self.intPageNo){(posts) in
self.posts = posts
}
}
.navigationBarTitle(Text("Home"))
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
/// Detail View Code:
import SwiftUI
struct ViewPostView: View {
#State var comments: [Comments] = []
#State var post: Posts
var body: some View {
NavigationView {
VStack{
Text(post.post_content)
.frame(alignment: .trailing)
List(comments){comment in
VStack(alignment: .trailing, spacing: 10){
HStack(spacing: 40){
Text(comment.author_name)
Text(comment.comment_date)
}
Text(comment.comment_content)
}
}
.frame(alignment: .trailing)
.onAppear {
PostViewManager().getComments(intPostID: self.post.id){(comments) in
self.comments = comments
}
}
}
}
}
}
struct ViewPostView_Previews: PreviewProvider {
static var previews: some View {
ViewPostView()
}
}
/// Fetching data Code:
struct Comments: Codable, Identifiable {
var id: Int
var author_name: String
var comment_content: String
var comment_date: String
var comment_date_gmt: String
var approved: String
}
class PostViewManager {
func getComments(intPostID: Int, completion: #escaping ([Comments]) -> ()){
guard let url = URL(string: "https://test.matjri.com/wp-json/matjri/v1/comments/\(intPostID)") else {return}
URLSession.shared.dataTask(with: url) { (data, _, _) in
let comments = try! JSONDecoder().decode([Comments].self, from: data!)
DispatchQueue.main.async {
completion(comments)
}
}
.resume()
}
}
struct Posts: Codable, Identifiable {
var id: Int
var title: String
var city_id: Int
var city_name: String
var category_id: Int
var category_name: String
var since: String
var author_id: String
var author_name: String
var post_content: String
var featured_image: String
}
class ApiPosts {
func getPosts(intPage: Int, completion: #escaping ([Posts]) -> ()){
guard let url = URL(string: "https://test.matjri.com/wp-json/matjri/v1/posts/0") else {return}
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Posts].self, from: data!)
DispatchQueue.main.async {
completion(posts)
}
}
.resume()
}
}
The error you get "Preview struct, ERROR: Missing argument for parameter", typically is because you did not provide the required parameters to the Preview.
ViewPostView expect to be passed "var post: Posts", so in ViewPostView_Previews you
need to provide that, for example:
struct ViewPostView_Previews: PreviewProvider {
static var previews: some View {
ViewPostView(post: Posts(id: 1, title: "title", ... ))
}
}