Iterating through array of structs based on json causes preview to fail - json

The code takes json data from list.json and I created a swift view BookView as another file.
import SwiftUI
struct product: Codable, Hashable {
var name: String
var author: String
var page: String
}
struct ContentView: View {
func jsonTwo() -> [product]{
let url = Bundle.main.url(forResource: "list", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
let products = try? decoder.decode([product].self, from: data)
return products!
}
#State var number: Int = 5
var body: some View {
NavigationView {
VStack {
Form {
ForEach(jsonTwo(), id: \.self) {item in
BookView(name: "item.name",author: "item.author",page: "item.page")
}
}
.navigationBarTitle("Books")
Button(action: {
// Button tapped
}, label: {
Image(systemName: "plus.rectangle")
.font(Font.system(.largeTitle).bold())
.foregroundColor(.primary)
})
.padding(.top)
}
}
}
}
This code give me the error "Cannot preview in this file -- Failed to update preview" but the following code works fine (difference in the ForEach statement) .Note I put the parameters for BookView in quotes to test.
import SwiftUI
struct product: Codable, Hashable {
var name: String
var author: String
var page: String
}
struct ContentView: View {
func jsonTwo() -> [product]{
let url = Bundle.main.url(forResource: "list", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
let products = try? decoder.decode([product].self, from: data)
return products!
}
#State var number: Int = 5
var body: some View {
NavigationView {
VStack {
Form {
ForEach(0..<number) {item in
BookView(name: "item.name",author: "item.author",page: "item.page")
}
}
.navigationBarTitle("Books")
Button(action: {
// Button tapped
}, label: {
Image(systemName: "plus.rectangle")
.font(Font.system(.largeTitle).bold())
.foregroundColor(.primary)
})
.padding(.top)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone 11")
}
}

Related

How to remove html tags from received JSON data that is shown in SwiftUI's List view?

I'm trying to get a book data description with a clean string which is currently in a raw JSON file. I've managed to get the data such as titles of the book, child trees, and descriptions. But while fetching the description, the data is in HTML tags such as .. etc. The below image shows what I'm currently getting as data.
and here is my code as below which I have used to call JSON book data and showed in SwiftUI's lists.
import SwiftUI
#main
struct nesteddemo_newApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
import SwiftUI
struct ContentView: View {
#State var book: Book = Book()
var body: some View {
NavigationView {
// alternatively
List {
ForEach(book.bookContent) { bookContent in
Section(header: Text(bookContent.title)) {
OutlineGroup(bookContent.child, children: \.child) { item in
Text(item.title)
}
}
}
}
ForEach(book.bookContent) { bookContent in
VStack {
Text(bookContent.title)
List(bookContent.child, children: \.child) { item in
Text(item.title)
}
}
}
}.navigationViewStyle(.stack)
.onAppear {
loadData()
}
}
func loadData() {
do {
if let url = Bundle.main.url(forResource: "સાગર મંથન", withExtension: "json") {
let data = try Data(contentsOf: url)
book = try JSONDecoder().decode(Book.self, from: data)
}
} catch {
print("error: \(error)")
}
}
struct Book: Identifiable, Codable {
let id = UUID()
var bookTitle: String = ""
var isLive: Bool = false
var userCanCopy: Bool = false
var bookContent: [BookContent] = []
enum CodingKeys: String, CodingKey {
case bookTitle = "book_title"
case isLive = "is_live"
case userCanCopy = "user_can_copy"
case bookContent = "book_content"
}
}
struct BookContent: Identifiable, Codable {
let id = UUID()
var title, type: String
var child: [Child]
}
struct Child: Identifiable, Codable {
let id = UUID()
var title, type: String
var child: [Child]?
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
func attributedString(from str: String) -> AttributedString {
if let theData = str.data(using: .utf16) {
do {
let theString = try NSAttributedString(data: theData, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
return AttributedString(theString)
} catch { print("\(error)") }
}
return AttributedString(str)
}
and replace Text(item.title) with Text(attributedString(from: item.title))
credits: #workingdog

How to solve error: 'Missing arguments for parameters 'data', 'detailedData' in call'

I'm trying to make a detailed view of a contact from a contacts list. This is the WIP of that view. I am getting the error;
Missing arguments for parameters 'data', 'detailedData' in call
on the line with ContactDetail() in the ContactDetail_Previews struct.
I think I understand that this is because something is missing from the variables data and detailedData, but my confusion comes from how I use similar code for the actual list view of all the contacts, with no such error. I have pasted the code for the whole list view below the code for the detailed view.
Any help would be appreciated!
Contact Detail Code:
import SwiftUI
struct ContactDetail: View {
var data: Response_Detailed.Contact_Detailed
#ObservedObject var detailedData: getData_Detailed
var body: some View {
VStack {
Text(data.first_name + " " + data.last_name)
Text(data.phone_number)
Text(data.birthday)
Text(data.address)
Text(data.updated_date)
Text(data.create_date)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.detailedData.updateDetailed_Data()
}
}
}
}
}
class getData_Detailed: ObservableObject {
#Published var data = [Response_Detailed.Contact_Detailed]()
#Published var id = 1
init() {
updateDetailed_Data()
}
func updateDetailed_Data() {
let url = "DATABASE_LINK\(id)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
do {
let json = try JSONDecoder().decode(Response_Detailed.self, from: data!)
let oldData = self.data
DispatchQueue.main.async {
self.data = oldData + json.data
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(json)
print(String(data: data, encoding: .utf8)!)
}
}
catch {
print(error.localizedDescription)
}
}.resume()
}
}
struct ContactDetail_Previews: PreviewProvider {
static var previews: some View {
ContactDetail()
}
}
struct Response_Detailed: Codable {
struct Contact_Detailed: Codable, Identifiable {
public let id: Int
public let first_name: String
public let last_name: String
public let birthday: String
public let phone_number: String
public let create_date: String
public let updated_date: String
public let address: String
}
public let data: [Contact_Detailed]
enum CodingKeys : String, CodingKey {
case data = "data"
}
}
Contacts List View Code: (Note the same error comes up on the line with NavigationLink.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
HStack {
ContactsList()
.navigationBarTitle("Contacts")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Image(systemName: "plus")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20.0)
}
}
}
}
}
}
struct ContactsList: View {
#ObservedObject var listData = getData()
var body: some View {
List(0..<listData.data.count, id: \.self) {i in
NavigationLink(destination: ContactDetail()) {
if i == self.listData.data.count - 1 {
cellView(data: self.listData.data[i], isLast: true, listData: self.listData)
}
else {
cellView(data: self.listData.data[i], isLast: false, listData: self.listData)
}
}
}
}
}
struct cellView: View {
var data: Response.Contact
var isLast: Bool
#ObservedObject var listData: getData
var body: some View {
VStack(alignment: .leading, spacing: 12) {
if self.isLast {
Text(data.first_name + " " + data.last_name)
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.title2)
.padding(/*#START_MENU_TOKEN#*/[.leading, .bottom, .trailing], 5.0/*#END_MENU_TOKEN#*/)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.listData.updateData()
}
}
}
else {
Text(data.first_name + " " + data.last_name)
.fontWeight(/*#START_MENU_TOKEN#*/.bold/*#END_MENU_TOKEN#*/)
.font(.title2)
.padding(/*#START_MENU_TOKEN#*/[.leading, .bottom, .trailing], 5.0/*#END_MENU_TOKEN#*/)
}
}
.padding(.top, 10)
}
}
class getData: ObservableObject {
#Published var data = [Response.Contact]()
#Published var limit = 15
#Published var skip = 0
init() {
updateData()
}
func updateData() {
let url = "DATABASE_LINK?skip=\(skip)&limit=\(limit)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
do {
let json = try JSONDecoder().decode(Response.self, from: data!)
let oldData = self.data
DispatchQueue.main.async {
self.data = oldData + json.data
self.limit += 15
self.skip += 15
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(json)
print(String(data: data, encoding: .utf8)!)
}
}
catch {
print(error.localizedDescription)
}
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Response: Codable {
struct Contact: Codable, Identifiable {
public let id: Int
public let first_name: String
public let last_name: String
public let updated_date: String
}
struct Pagination_Data: Codable {
public let skip: Int
public let limit: Int
public let total: Int
}
public let data: [Contact]
public let pagination: Pagination_Data
enum CodingKeys : String, CodingKey {
case data = "data"
case pagination = "pagination"
}
}
Your ContactsList is giving the only variable an initial value.
#ObservedObject var listData = getData()
Not IAW Apple documentation but the = getData() is the initial value
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
BTW you should change it to
#StateObject var listData = getData()
Your ContactDetail View has two variables without an initial value
struct ContactDetail: View {
var data: Response_Detailed.Contact_Detailed
#ObservedObject var detailedData: getData_Detailed
No = sign so a struct creates an init that looks like this
init(data: Response_Detailed.Contact_Detailed, detailedData: getData_Detailed)
So in your Preview you need to provide the initial values
ContactDetail(data: /.../, detailedData: /.../)
The /.../ symbolizes where you will provide sample data

SwiftUI local JSON save string value

I created a local json. I change the value of the name key in the json and when I close and open the application, it says "Test" again. How can I save the change I made on the Json file?
Why can't I save the string value? I shared all the codes with you. If you want I can share the project.
Local JSON File
{
"person": {
"name": "Test"
}
}
Model
struct PersonContainer: Codable {
var person: Person?
}
struct Person: Codable {
var name: String?
}
JSON Provider
class JSONProvider: ObservableObject {
#Published var personContainer: PersonContainer = PersonContainer()
var fm = FileManager.default
var fresult: Bool = false
#Published var subUrl: URL? = URL(string: "")
var mainUrl: URL? = Bundle.main.url(forResource: "test", withExtension: "json")
func getData() {
do {
let documentDirectory = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
subUrl = documentDirectory.appendingPathComponent("test.json")
loadFile(mainPath: mainUrl!, subPath: subUrl!)
} catch {
print(error)
}
}
func loadFile(mainPath: URL, subPath: URL){
if fm.fileExists(atPath: subPath.path){
decodeData(pathName: subPath)
if ((personContainer.person) != nil) {
decodeData(pathName: mainPath)
}
}else{
decodeData(pathName: mainPath)
}
}
func decodeData(pathName: URL){
do{
let jsonData = try Data(contentsOf: pathName)
let decoder = JSONDecoder()
let personContainer = try decoder.decode(PersonContainer.self, from: jsonData)
self.personContainer = personContainer
} catch {}
}
func writeToFile(location: URL) {
do{
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let JsonData = try encoder.encode(personContainer)
try JsonData.write(to: location)
} catch {
}
}
}
ContentView
struct ContentView: View {
#State var text: String = ""
#ObservedObject var jsonProvider: JSONProvider = JSONProvider()
var body: some View {
VStack {
TextField("Placeholder", text: $text)
.padding()
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
Text("Hello, world! \(jsonProvider.personContainer.person?.name ?? "")")
.padding()
Button(action: {
jsonProvider.personContainer.person?.name = text
jsonProvider.writeToFile(location: jsonProvider.subUrl!)
}) {
Text("Button")
}
}
.onAppear {
jsonProvider.getData()
}
}
}
Looks like you were on the right track, but there were a few things missing.
Since the original main bundle's test.json should only be loaded if the file in the documents directory doesn't exist, a lot of the logic can be simplified. For example, you can remove the #Published subUrl, since it never gets changed and isn't observed by the View.
Make sure that you call the writeToFile when the button is pressed.
Also, it's always a good idea to do something (like printing the error) inside the catch blocks in case something has gone wrong.
class JSONProvider: ObservableObject {
#Published var personContainer: PersonContainer = PersonContainer()
private var fm = FileManager.default
private let mainUrl: URL = Bundle.main.url(forResource: "test", withExtension: "json")!
func getData() {
if fm.fileExists(atPath: documentDirectoryJSONURL().path) {
decodeData(fromURL: documentDirectoryJSONURL())
} else {
decodeData(fromURL: mainUrl)
}
}
func documentDirectoryJSONURL() -> URL {
do {
let documentDirectory = try fm.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
return documentDirectory.appendingPathComponent("test.json")
} catch {
fatalError("Couldn't create URL")
}
}
func decodeData(fromURL url: URL){
do{
let jsonData = try Data(contentsOf: url)
let decoder = JSONDecoder()
let personContainer = try decoder.decode(PersonContainer.self, from: jsonData)
self.personContainer = personContainer
} catch {
print(error)
assertionFailure("Error decoding JSON")
}
}
func writeToFile() {
do{
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(personContainer)
try jsonData.write(to: documentDirectoryJSONURL())
} catch {
print(error)
}
}
}
struct ContentView: View {
#State var text: String = ""
#ObservedObject var jsonProvider: JSONProvider = JSONProvider()
var body: some View {
VStack {
TextField("Placeholder", text: $text)
.padding()
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(15)
.padding(.horizontal)
Text("Hello, world! \(jsonProvider.personContainer.person?.name ?? "")")
.padding()
Button(action: {
jsonProvider.personContainer.person?.name = text
jsonProvider.writeToFile()
}) {
Text("Write")
}
}
.onAppear {
jsonProvider.getData()
}
}
}

Why is my JSON code in Swift not parsing?

Disclaimer: Very basic question below. I am trying to learn the basics of IOS development.
I'm currently trying to parse data from an API to a SwiftUI project and am not able to successfully do so.
The code goes as follows:
import SwiftUI
struct Poem: Codable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
let joined = fetch.poem.lines.joined(separator: "\n")
var body: some View {
Text(fetch.poem.title)
.padding()
Text( \(joined) )
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The build currently fails. It's throwing me the following errors:
Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol'
Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'ObservedObject<FetchPoem>.Wrapper'
Value of type 'FetchPoem' has no dynamic member 'poem' using key path from root type 'FetchPoem'
Moreover, I am attempting to append the array "Lines" into one main String variable "Joined". However, I am not sure this works... The error is "String interpolation can only appear inside a string literal". Would love some help if anyone knows...
Any ideas? All help is appreciated.
** Edited Code - Q2
import SwiftUI
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
#Published var poems = [Poem]()
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
VStack {
if let poem = fetch.poems.first {
Button("Refresh") {getPoem}
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Most probably you wanted this (because poems is an array) - tested with Xcode 12.1 / iOS 14.1
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
// ...
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
List {
ForEach(fetch.poems, id: \.self) {
Text("\($0.author): \($0.title)")
}
}
}
}
... , and next might be wrapping that into NavigationView/NavigationLink with shown each poem in desination view.
Of showing lines as follows:
var body: some View {
VStack {
if let poem = fetch.poems.first {
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}

Decode JSON using Codable and then populate my SwiftUI

I am new to Swift and SwiftUI. I am currently teaching myself how to code (loving it!) through hackingwithswift.com I am currently on Day 60 and I am stuck and not sure what to do from here.
The challenge is to decode some information using Codable and populate SwiftUI.
I created a struct to match the JSON, but when I go to run the app, I keep getting my error "Fetch Failed: Unknown Error" and therefore my UI won't update.
Would someone glance at my code and provide any pointers on where I am going wrong and possibly why? Thank you so much for any suggestions and help, it is much appreciated! Code is posted below.
Cody
import SwiftUI
struct Response: Codable {
var results: [User]
}
struct User: Codable, Identifiable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: String
let tags: [String]
struct FriendRole: Codable {
let id: String
let name: String
}
let friend: [FriendRole]
}
struct ContentView: View {
#State private var results = [User]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.address)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") 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.results = decodedResponse.results
}
return
}
}
print("Fetch Failed: \(error?.localizedDescription ?? "Unkown Error").")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm also new to swiftUI this is how I managed to make it work by using Observable Objects
Here's the structures Since in the json file there is a [ in the very beginning you do not have to create a struct that has a User array you could create a struct and then create a variable of that struct type as an array
here's how I did it
I created separate files for instance for the structures i had a different file for them
here's what I have for my structure file
import Foundation
struct User : Codable, Identifiable {
let id : String
let isActive : Bool
let name : String
let age : Int
let company : String
let email : String
let address : String
let about : String
let registered : String
let tags = [String]()
let friends = [Friends]()
}
struct Friends : Codable {
let id : String
let name : String
}
I created another file for the observable object class
class JsonChannel : ObservableObject {
#Published var retVal = [User]()
func getInfo () {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {return}
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let data = data {
DispatchQueue.main.async {
do {
self.retVal = try JSONDecoder().decode([User].self, from: data)
}
catch {
print(error)
}
}
}
}.resume()
}
}
and here's what i have for my contentView file
import SwiftUI
struct ContentView : View {
#ObservedObject var info = JsonChannel()
var body: some View {
VStack {
Button(action: {
self.info.getInfo()
}) {
Text("click here to get info")
}
List {
ForEach (self.info.retVal) { item in
VStack {
Text("\(item.name)")
Text("\(item.address)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}