在 SwiftUI 列表中显示 JSON 字典

Posted

技术标签:

【中文标题】在 SwiftUI 列表中显示 JSON 字典【英文标题】:Show JSON Dictionary in SwiftUI List 【发布时间】:2020-07-28 15:34:52 【问题描述】:

我正在尝试在我的 swift 应用程序中迭代对象字典并将它们显示在列表中。我不断收到“[Note]”类型的错误值没有成员“prettyUpdatedString”。我哪里错了?

更新:感谢社区成员的输入,我已经能够编译代码,但数据仍然没有显示在列表中。没有抛出任何错误,但该区域只是空白。请参阅屏幕截图。

数据:

[

id: 2867,
type: "TechNote",
isSolution: false,
prettyUpdatedString: "1 day ago <strong>Cody Brown</strong> said",
mobileNoteText: "Attempted contact, left voicemail no anwser.",
isTechNote: true,
isHidden: false,
workTime: "0"
,

id: 2863,
type: "TechNote",
isSolution: true,
prettyUpdatedString: "1 day ago <strong>Cody Brown</strong> said",
mobileNoteText: "Computer is repaired and ready to be picked up. Please come to the High School Wednesdays (1pm-2pm). If you have a loaner computer please bring it along with the the student.",
isTechNote: true,
isHidden: false,
workTime: "0"
,

id: 2818,
type: "TechNote",
isSolution: false,
prettyUpdatedString: "2 weeks ago <strong>Cody Brown</strong> said",
mobileNoteText: "Sent to AGI for repair.",
isTechNote: true,
isHidden: false,
workTime: "0"
,

id: 2814,
type: "TechNote",
isSolution: false,
prettyUpdatedString: "2 weeks ago <strong>Cody Brown</strong> said",
mobileNoteText: "Brought to Office",
isTechNote: true,
isHidden: false,
workTime: "0"
,

id: 2790,
type: "TechNote",
isSolution: false,
prettyUpdatedString: "2 weeks ago <strong>Seth Duncan</strong> said",
mobileNoteText: "Left VM",
isTechNote: true,
isHidden: true,
workTime: "0"
,

id: 2717,
type: "TechNote",
isSolution: false,
prettyUpdatedString: "2 weeks ago <strong>Seth Duncan</strong> said",
mobileNoteText: "<br/> We will be at the OHS Front Office from 1-2PM on Wednesdays to assist with computer issues",
isTechNote: true,
isHidden: false,
workTime: "0"

]

获取数据

import SwiftUI

struct Note: Decodable, Identifiable 
    var id: Int
    var prettyUpdatedString: String
    var mobileNoteText: String


class FetchTicketNotes: ObservableObject 
    func getTicketNotes(id: Int, userApi: String, completion: @escaping ([Note]) -> ()) 
        guard let url = URL(string: "URL HERE") else  return 
        URLSession.shared.dataTask(with: url) (data, _, _) in
            let ticketNotes = try! JSONDecoder().decode([Note].self, from: data!)
            DispatchQueue.main.async 
                completion(ticketNotes)
            
        
        .resume()
    

在视图中

import SwiftUI

struct DetailsView: View 
    @ObservedObject var ticketStatusAction = TicketStatusAction()
    @ObservedObject var createTicketNote = CreateTicketNote()
    @State var ticket: [TicketDetails] = []
    @State var ticketNotes: [Note] = []
    @State private var showingNoteAlert = false
    @State private var showingOpenAlert = false
    @State private var showingPendingAlert = false
    @State private var showingDepotAlert = false
    @State private var showingCloseAlert = false
    @State private var note: String = ""
    @ObservedObject private var keyboard = KeyboardResponder()

    var id: Int
    var displayClient: String
    @Binding var userApi: String
    
    var body: some View 
        ScrollView(.vertical, showsIndicators: false)
            VStack(alignment: .leading)
                if !ticket.isEmpty 
                           Text(self.ticket.first?.location.locationName ?? "")
                                .fontWeight(.bold)
                                .padding()
                           
                
                Text("\(displayClient) - \(id)")
                    .fontWeight(.bold)
                    .font(.system(size:20))
                    .padding()

                Divider()
                
                Text("Status")
                    .fontWeight(.bold)
                    .padding()
                
                if !ticket.isEmpty 
                    Text(self.ticket.first?.statustype.statusTypeName ?? "")
                    .padding()
                
                
                Text("Details")
                .fontWeight(.bold)
                .padding()
                
                if !ticket.isEmpty 
                    Text(clearMarkdown(on: self.ticket.first?.detail ?? ""))
                        .padding()
                        .fixedSize(horizontal: false, vertical: true)
                    
                    Text("Room Number")
                    .fontWeight(.bold)
                    .padding()
                    
                    Text(self.ticket.first?.ticketCustomFields.first(where: $0.definitionId == 14)?.restValue ?? "NOT PROVIDED")
                    .padding()
                    
                    Text("Computer Number")
                    .fontWeight(.bold)
                    .padding()
                    
                    Text(self.ticket.first?.ticketCustomFields.first(where: $0.definitionId == 11)?.restValue ?? "NOT PROVIDED")
                    .padding()
                
                    Text("Phone Number")
                    .fontWeight(.bold)
                    .padding()
                    
                    Text(self.ticket.first?.ticketCustomFields.first(where: $0.definitionId == 15)?.restValue ?? "NOT PROVIDED")
                    .padding()
                
                
                
                Divider()
                
                Text("Notes")
                .fontWeight(.bold)
                .padding()
                
                List(ticketNotes)  ticketNote in
                    VStack(alignment: .leading, spacing: 10) 
                        Text(ticketNote.prettyUpdatedString)
                        .padding()
                        
                        Text(ticketNote.mobileNoteText)
                        .padding()
                        .fixedSize(horizontal: false, vertical: true)
                    
                
            
            .onAppear 
                FetchTicketNotes().getTicketNotes(id: self.id, userApi: self.userApi)  ticketNotes in
                    self.ticketNotes = ticketNotes
                
                FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                    self.ticket = [ticketDetails]
                
            
            
            Divider()

               Section(header: Text("Create New Note")
                   .fontWeight(.bold)
                   .padding()
                   .padding(10)
                   .frame(maxWidth: .infinity)) 
                       
                   TextField("Enter your note", text: $note)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .frame(width: 350)
                    .padding(15)

                   
                   Button(action: 
                       self.showingNoteAlert = true
                   ) 
                       Text("Submit Note")
                       .frame(width: 300)
                       .padding(15)
                       .foregroundColor(Color.white)
                       .background(Color.orange)
                       .cornerRadius(5)
                   .buttonStyle(BorderlessButtonStyle()
                   ).actionSheet(isPresented:self.$showingNoteAlert) 
                       ActionSheet(
                           title: Text("Are you sure you want to add this note to \(displayClient)'s ticket?"),
                           message: Text("\(self.note)"),
                           buttons: [
                        .default(Text("Submit"))
                            
                                self.createTicketNote.CreateNoteAction(ticketId: self.id, userApi: self.userApi, techNote: self.note);
                                self.note = "";
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
                                    FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                                        self.ticket = [ticketDetails]
                                    
                                
                            ,
                        .cancel()
                            self.note = ""
                            ])
                   
               
            
            Divider()

            Section(header: Text("Change Ticket Status")
                .fontWeight(.bold)
                .padding()
                .padding(10)
                .frame(maxWidth: .infinity)) 
                                    
                Button(action: 
                    self.showingOpenAlert = true
                ) 
                    Text("Open")
                    .frame(width: 300)
                    .padding(15)
                    .foregroundColor(Color.white)
                    .background(Color.green)
                    .cornerRadius(5)
                .buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingOpenAlert) 
                    Alert(
                        title: Text("Are you sure you want change \(displayClient)'s ticket to Open?"),
                        primaryButton: .default(Text("Open"))
                            
                                self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 1);
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
                                    FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                                        self.ticket = [ticketDetails]
                                    
                                
                            ,
                        secondaryButton: .cancel())
                
                Spacer()
                Button(action: 
                    self.showingPendingAlert = true
                ) 
                    Text("Pending")
                    .frame(width: 300)
                    .padding(15)
                    .foregroundColor(Color.white)
                    .background(Color.yellow)
                    .cornerRadius(5)
                .buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingPendingAlert) 
                    Alert(
                        title: Text("Are you sure you want to set \(displayClient)'s ticket to Pending?"),
                        primaryButton: .default(Text("Pending"))
                            
                                self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 2);
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
                                    FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                                        self.ticket = [ticketDetails]
                                    
                                
                            ,
                        secondaryButton: .cancel())
                
                Spacer()
                Button(action: 
                    self.showingDepotAlert = true
                ) 
                    Text("Depot")
                    .frame(width: 300)
                    .padding(15)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .cornerRadius(5)
                    
                .buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingDepotAlert) 
                    Alert(
                        title: Text("Are you sure you want to depot \(displayClient)'s ticket?"),
                        primaryButton: .default(Text("Depot"))
                            
                                self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 6);
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
                                    FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                                        self.ticket = [ticketDetails]
                                    
                                
                            ,
                        secondaryButton: .cancel())
                
                Spacer()
                Button(action: 
                    self.showingCloseAlert = true
                ) 
                    Text("Close")
                    .frame(width: 300)
                    .padding(15)
                    .foregroundColor(Color.white)
                    .background(Color.red)
                    .cornerRadius(5)
                .buttonStyle(BorderlessButtonStyle()).alert(isPresented:self.$showingCloseAlert) 
                    Alert(
                        title: Text("Are you sure you want to close \(displayClient)'s ticket?"),
                        primaryButton: .destructive(Text("Close"))
                            
                                self.ticketStatusAction.TicketAction(ticketId: self.id, userApi: self.userApi, desiredStatus: 3);
                                DispatchQueue.main.asyncAfter(deadline: .now() + 1) 
                                    FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                                        self.ticket = [ticketDetails]
                                    
                                
                            ,
                        secondaryButton: .cancel())
                
                Spacer()
            
        .padding()
        .padding(.bottom, keyboard.currentHeight)
        .edgesIgnoringSafeArea(.bottom)
        .animation(.easeOut(duration: 0.16))
    

【问题讨论】:

解码部分无法工作。根对象是一个数组(注意封闭的[]),既没有键note也没有id 好的,如果我无法解码,我将如何显示数据? 删除结构 TicketNotes 并解码 [Note].self 并删除 Text(clearMarkdown(on: note.prettyUpdatedString)) 中的一个 note 和另一个出现。而且你必须调整一些类型。 【参考方案1】:

这里有很多错误。首先将getTicketNotes方法参数的解码完成块更新为[TicketNotes]

class FetchTicketNotes: ObservableObject 
    func getTicketNotes(id: Int, userApi: String, completion: @escaping ([Note]) -> ()) 
        guard let url = URL(string: "LINK HERE") else  return 
        URLSession.shared.dataTask(with: url) (data, _, _) in
            let ticketNotes = try! JSONDecoder().decode([Note].self, from: data!)
            DispatchQueue.main.async 
                completion(ticketNotes)
            
        
        .resume()
    

然后修改.onAppear

.onAppear 
    //...
    FetchTicketNotes().getTicketNotes(id: self.id, userApi: self.userApi)  ticketNotes in
        self.ticketNotes = ticketNotes
    

最后,

@State var ticketNotes: [Note] = []
//...
Text(clearMarkdown(on: note.prettyUpdatedString))

【讨论】:

检查更新。您需要解码[Note] 并在DetailsView 中更新ticketNotes 所以,好消息是,这不会引发任何错误,我可以在调试时看到数据,但坏消息是数据没有显示在应用程序中。我会更新代码和截图。【参考方案2】:

这可能不是最好的解决方案,但它适用于我的情况。我确实在 [TicketDetails] JSON 中找到了我正在寻找的数据,因此它使这变得不那么复杂。在我的 .onAppear 函数中,我捕获了相关数据。

声明变量

@State var ticketNotes: [Notes] = []

在出现时更改

.onAppear 
                FetchTick().getTicket(id: self.id, userApi: self.userApi)  (ticketDetails) in
                    self.ticket = [ticketDetails]
                    self.ticketNotes = ticketDetails.notes
                
            

我用 ForEach 遍历了数组中的每一项

        VStack(alignment: .leading, spacing: 10) 
            ForEach(ticketNotes, id: \.self) ticketNote in
                Text(clearMarkdown(on: "\(ticketNote.prettyUpdatedString) '\(ticketNote.mobileNoteText)'"))
                .padding()
            
        

【讨论】:

以上是关于在 SwiftUI 列表中显示 JSON 字典的主要内容,如果未能解决你的问题,请参考以下文章

带有使用数组字典的部分的 SwiftUI 列表

来自 JSON/Dictionary 的 SwiftUI 列表

如何在切换 SwiftUI 中使用 json 属性?

从公共 API 获取 JSON 以在 SwiftUI 的列表视图中呈现

如何将字典项分别添加到 SwiftUI 列表中

SwiftUI 使用 List 和 Foreach 遍历字典中的键并创建列表视图