Toggle sorting in List View - json

I use 'Api' as an ObervableObject and inside there's #Published var isAscd to be toggled. Function getPost fetch JSON data from web and populate is List View. I use a button in HeaderView to toggle the sorting method. Compiling is succeeded but button takes no action. I don't know what goes wrong?
class Api: ObservableObject {
#Published var posts: [Post] = []
#Published var isAscd: Bool = false
func getPosts(completion: #escaping ([Post]) -> ()) {
guard let url = URL(string: getListURLString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
completion(posts)
}
}
.resume()
}
}
struct HeaderView: View {
var holding: String = "市值/數量"
var earning: String = "現價/成本"
var profit: String = "持倉賺蝕"
#ObservedObject var api = Api()
var body: some View {
HStack{
Button(action: {
self.api.isAscd.toggle()
if self.api.isAscd {
self.api.posts.sort { $0.stockcode > $1.stockcode}
} else { self.api.posts.sort { $0.stockcode < $1.stockcode}
}
print(self.api.posts.count)
print(self.api.isAscd)
}.......}
List {
Section(header: HeaderView())
{
ForEach(posts)
{ post in
HStack {
VStack(alignment: .leading)
..........
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 250, alignment: .center)
.onAppear {
Api().getPosts {(posts) in
self.posts = posts
//self.apr.isAscd.toggle()
}
}
}

Related

How to set dat to the View from 2D Array type Api in SwiftUI?

I have 2D type Array data in Json File And wants to fetch the data in View Model And I tried many Time and unable to fetch the data I used the Model which generated from the QuickType.io And I use the MVVM pattern for fetching the data.
This is My Json Data.
{ "publisherPlans": [[ "Name", "Free","Prime" ],["Book Sell",9999,9999],["Book Bulk Sell",0,9999],["Magazine start-up",9999,9999],["Demo book request count for School",5,9999],["Demo book request Acception",9999,9999],["Assign book for demo",25,9999]]}
And this is My Model which generated By website
public struct SchoolPlanModel: Decodable {
public let publisherPlans: [[PublisherPlan]]
}
public enum PublisherPlan:Decodable {
case integer(Int)
case string(String)
}
And this is My ViewModel here I am trying to fetch data from Json file
class ReadData: ObservableObject {
#Published var datas = [String]()
func getData() async {
guard let url = URL(string: "********api/plans") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
Task{#MainActor in
let results = try JSONDecoder().decode(SchoolPlanModel.self, from: data).publisherPlans
print(results)
}
} catch {
print("---> error: \(error)")
}
}
}
This My View Here I want to show Data.
struct SchoolPlanView: View{
#StateObject var list = ReadData()
var body: some View{
ForEach(list.datas,id: \.self) { array in
HStack{
ForEach(array.utf8CString, id: \.self) { element in
Text("\(element)")
}
}
}
}
}
try this approach, works for me. I had to modify a few things in your code, but should be straightforward to understand. Let me know if you need more explanations.
struct ContentView: View {
var body: some View {
SchoolPlanView()
}
}
struct SchoolPlanModel: Decodable {
var publisherPlans: [[PublisherPlan]]
}
enum PublisherPlan: Decodable, Hashable {
case integer(Int)
case string(String)
// -- here
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(PublisherPlan.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Plan"))
}
}
class ReadData: ObservableObject {
#Published var datas: [[PublisherPlan]] = [] // <-- here
func getData() async {
guard let url = URL(string: "https://myexampleurl.com") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
Task{#MainActor in
let results = try JSONDecoder().decode(SchoolPlanModel.self, from: data)
self.datas = results.publisherPlans // <-- here
}
} catch {
print("---> error: \(error)")
}
}
}
struct SchoolPlanView: View{
#StateObject var list = ReadData()
var body: some View {
// -- here
List(list.datas, id: \.self) { array in
HStack{
ForEach(array, id: \.self) { item in
switch item {
case .integer(let int): Text("\(int)")
case .string(let str): Text(str)
}
}
}
}
.task {
await list.getData() // <-- here
}
}
}
EDIT-1:
To get the headings for columns, try this approach, where
you separate the data and headings at the source, in getData().
You will have to adjust the display using columns etc...
class ReadData: ObservableObject {
#Published var datas: [[PublisherPlan]] = []
#Published var headings: [String] = [] // <-- here
func getData() async {
guard let url = URL(string: "https://myexampleurl.com/plans") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
// print("\n \(String(data: data, encoding: .utf8) as AnyObject) \n")
Task{#MainActor in
let results = try JSONDecoder().decode(SchoolPlanModel.self, from: data)
// the data minus the first array of headings
datas = Array(results.publisherPlans.dropFirst()) // <-- here
// get the headings
if let headers = results.publisherPlans.first { // <-- here
for h in headers {
switch h {
case .integer(_): break
case .string(let str): self.headings.append(str)
}
}
}
}
} catch {
print("---> error: \(error)")
}
}
}
struct SchoolPlanView: View{
#StateObject var list = ReadData()
var body: some View {
VStack {
HStack {
ForEach(list.headings, id: \.self) { heading in // <-- here
Text(heading)
}
}
List(list.datas, id: \.self) { array in
HStack {
ForEach(array, id: \.self) { item in
switch item {
case .integer(let int): Text("\(int)")
case .string(let str): Text(str)
}
}
}
}
}
.task {
await list.getData()
}
}
}

Create a custom AsyncSequence

NOTE: Currently config: Xcode 13 beta 3 (13A5192i), macOS Montery (21A5284e).
Trying to asynchronously initialize data from a json.
The code apparently works fine, but the elements are not added to #Published public private (set) var users = [User]()
Is it something stupid I don't see or is it just another bug?
[
{
"id": 1,
"name":"gio"
},
{
"id": 3,
"name":"mimi"
},
{
"id": 2,
"name":"pepo"
},
{
"id": 4,
"name":"bassix"
},
{
"id": 5,
"name":"peponews"
}
]
import SwiftUI
struct URLWatcher: AsyncSequence, AsyncIteratorProtocol {
typealias Element = Data
let url: URL
let delay: Int
private var comparisonData: Data?
init(url: URL, delay: Int = 10) {
self.url = url
self.delay = delay
}
mutating func next() async throws -> Data? {
if comparisonData == nil {
comparisonData = try await fetchData()
} else {
while true {
await Task.sleep(UInt64(delay) * 1_000_000_000)
let latestData = try await fetchData()
if latestData != comparisonData {
comparisonData = latestData
break
}
}
}
if comparisonData == nil {
return nil
} else {
return comparisonData
}
}
private func fetchData() async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
func makeAsyncIterator() -> URLWatcher {
self
}
}
struct User: Identifiable, Decodable {
let id: Int
let name: String
}
#MainActor
class UserData: ObservableObject {
static let shared = UserData()
#Published public private(set) var users = [User]()
func fetchUsers() async {
let url = Bundle.main.url(forResource: "items", withExtension: "json")
let urlWatcher = URLWatcher(url: url!, delay: 3)
do {
for try await data in urlWatcher {
try withAnimation {
let data = try JSONDecoder().decode([User].self, from: data)
print(data)
users = data
}
}
} catch {
print("error \(error)")
}
}
private init() {
// Begin loading the data
Task {
await fetchUsers()
}
}
}
struct TestAsync: View {
let data = UserData.shared
var body: some View {
List(data.users) { user in
Text(user.name)
}
}
}
try this:
struct TestAsync: View {
#StateObject var data = UserData.shared // <-- or #ObservedObject
var body: some View {
List(data.users) { user in
Text(user.name)
}
}
}

How to change the url string with inputs from the user in SwiftUI

i am making this app that uses jikan API to show a list of animes, so in the url there is an option to change stuff like the type - anime, manga, etc and the subtype - upcoming, tv, movie, etc, the API is working working fine and is fetching details but now I want to show two pickers in a form view preferably to allow the user to select type and subtype thus I have used #State properties for the picker but it's not updating the list when I run the app and select a different value from the picker
here is the code -
import SwiftUI
struct Response: Codable{
var top: [Result]
}
struct Result: Codable {
var mal_id: Int
var rank: Int
var title: String
var type: String
var start_date: String?
var image_url: String
}
struct ContentView: View {
#State private var str2 = ""
#State private var str3 = ""
func loadData() {
str3 = String("/\(subtype[subTypeSelection])")
str2 = String("\(type[typeSelection])/")
let str1 = "https://api.jikan.moe/v3/top/"
guard let url = URL(string: str1 + str2 + "\(1)" + str3) 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) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.top = decodedResponse.top
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
var type = ["anime", "manga", "people", "characters"]
var subtype = ["airing", "upcoming", "tv", "movie", "ova", "special"]
#State private var page = 1
#State private var typeSelection = 0
#State private var subTypeSelection = 2
#State private var top = [Result]()
var body: some View {
// ScrollView {
VStack {
Picker("Your Selection", selection: $subTypeSelection) {
ForEach(0 ..< 6) {
somehting in
Text("\(subtype[somehting])")
}
}
Picker("Your Selection", selection: $typeSelection) {
ForEach(0 ..< 4) {
somehting in
Text("\(type[somehting])")
}
}
}
// .onAppear(perform: {
// loadData()
// })
List(top, id: \.mal_id) { item in
HStack {
AsyncImage(url: URL(string: item.image_url)!,
placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
// .frame(idealHeight: UIScreen.main.bounds.width / 10 * 10 )
.clipShape(Capsule())
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(String("\(item.rank)"))
.font(.headline)
Text(item.type)
.font(.headline)
Text(item.start_date ?? "")
.font(.headline)
}
}
}
// }
.onAppear(perform: loadData)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
you could try to use "onChange" on each picker:
VStack {
Picker("Your subtype Selection", selection: $subTypeSelection) {
ForEach(0 ..< 6) { somehting in
Text("\(subtype[somehting])").tag(somehting)
}
}.onChange(of: subTypeSelection) { value in
loadData()
}
Picker("Your type Selection", selection: $typeSelection) {
ForEach(0 ..< 4) { somehting in
Text("\(type[somehting])").tag(somehting)
}
}.onChange(of: typeSelection) { value in
loadData()
}
}
It's not very efficient, but I'm sure you will find a better way to re-load the data.

Problems with JSON visualization in SwiftUI App

I'm working on my first app, which loads a JSON with the elements that need to be displayed.
At the moment everything seems to work, except a few details which I hope you can help me solve:
My interface is composed of a TabView contained within a NavigationView (which contains a button that brings up a SheetView) ... if I go to the Tab that brings up the list with the contents downloaded from the JSON everything works, but if I move to another Tab and then I go back to the one that displays the JSON, the data is no longer visible.
I obviously inserted in the code (where necessary) ObservableObject, ObservedObject and onAppear that should reload the JSON, but without success ... it is clear that something is missing ...
How could I go about displaying a list of categories starting from a JSON field? Let me explain, I used the "autore" field of the JSON to define categories, so I would need to first display a list of these categories, then, entering the respective categories, I have to display a list with the elements relating to the selected category and then obviously the DetailView for the single elements.
Of course, since is my first time writing here, ask me everything you need to help me solving the problem...
This is the relevant code:
Code: BussoModel.swift
import SwiftUI
class BussoModel: Identifiable {
var id: UUID = UUID()
var titolo: String = ""
var autore: String = ""
var testo: String = ""
var data: String = ""
var extra1: String = ""
var extra2: String = ""
var fotoUrl: String = ""
}
Code: DownloadManager.swift
import SwiftUI
import Combine
import Alamofire
class DownloadManager: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
var storage : [BussoModel] = []
func scaricaJSON() {
AF.request("http://geniuspointfrezza.altervista.org/index.php?json=1").responseJSON { response in
if let errore = response.error {
debugPrint(errore.localizedDescription)
return
}
guard let ilJson = response.value else { return }
guard let json = JSON(ilJson).array else { return }
self.storage = []
let totale = json.count
for i in 0..<totale {
let busso = BussoModel()
if let titolo = json[i]["titolo"].string {
busso.titolo = titolo
}
if let autore = json[i]["autore"].string {
busso.autore = autore
}
if let testo = json[i]["testo"].string {
busso.testo = testo
}
if let data = json[i]["data"].string {
busso.data = data
}
if let extra1 = json[i]["extra1"].string {
busso.extra1 = extra1
}
if let extra2 = json[i]["extra2"].string {
busso.extra2 = extra2
}
if let foto = json[i]["fotoUrl"].string {
busso.fotoUrl = foto
}
self.storage.append(busso)
}
self.objectWillChange.send()
}
}
}
Code: CategoryView.swift
import SwiftUI
import URLImage
struct CategoryView: View {
#ObservedObject var dm: DownloadManager
var body: some View {
List {
ForEach(dm.storage) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
.clipped()
.padding()
VStack(alignment: .leading) {
Text(busso.titolo)
.font(.headline)
Text(busso.autore)
.font(.subheadline)
}
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
.onAppear {
self.dm.scaricaJSON()
}
}
}
struct CategoryView_Previews: PreviewProvider {
static var previews: some View {
CategoryView(dm: DownloadManager())
}
}
Code: MainTabView.swift
import SwiftUI
struct MainTabView: View {
#State private var selected = 0
var body: some View {
TabView(selection: $selected) {
HomeView()
.tabItem {
Image(systemName: (selected == 0 ? "house.fill" : "house"))
Text("Home")
}.tag(0)
CategoryView(dm: DownloadManager())
.tabItem {
Image(systemName: (selected == 1 ? "text.justify" : "text.justify"))
Text("Categorie")
}.tag(1)
Text("Galleria?")
.tabItem {
Image(systemName: (selected == 2 ? "photo.fill" : "photo"))
Text("Galleria")
}.tag(2)
Text("Preferiti?")
.tabItem {
Image(systemName: (selected == 3 ? "star.fill" : "star"))
Text("Preferiti")
}.tag(3)
}
.accentColor(.white)
.onAppear() {
UINavigationBar.appearance().barTintColor = UIColor(red: 115.0/255.0, green: 90.0/255.0, blue: 143.0/255.0, alpha: 1.0)
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UITabBar.appearance().barTintColor = UIColor(red: 115.0/255.0, green: 90.0/255.0, blue: 143.0/255.0, alpha: 1.0)
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Code: ContentView.swift
import SwiftUI
import URLImage
let furl = URL(fileURLWithPath: "path")
struct ContentView: View {
#State var showInfoView = false
var body: some View {
NavigationView {
MainTabView()
.sheet(isPresented: $showInfoView) {
InfoView(showInfoView: $showInfoView)
}
.navigationBarTitle((Text("ViviBusso")), displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
debugPrint("QR Code")
}) {
Image(systemName: "qrcode")
}
.foregroundColor(.white), trailing: Button(action: {
self.showInfoView.toggle()
}) {
Image(systemName: "info.circle")
}
.foregroundColor(.white)
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Waiting for suggestions, thanks!
EDIT: New code tested:
import SwiftUI
import URLImage
struct Busso: Codable, Identifiable {
public var id: UUID
public var titolo: String
public var autore: String
public var testo: String
public var data: String
public var extra1: String
public var extra2: String
public var foto: String
public var fotoUrl: String
}
class FetchBusso: ObservableObject {
#Published var Bussos = [Busso]()
init() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([Busso].self, from: bussoData)
DispatchQueue.main.async {
self.Bussos = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct CategoryView: View {
#ObservedObject var fetch = FetchBusso()
var body: some View {
VStack {
List(fetch.Bussos) { todo in
VStack(alignment: .leading) {
Text(todo.titolo)
}
}
}
}
}
struct CategoryView_Previews: PreviewProvider {
static var previews: some View {
CategoryView()
}
}

Swift ui macos sort json by obj

I make a json call to get some data from as seen from the image, but I have to sort these items by stargazers_count, ie the items with the largest stargazers_count put before.
Can you give me a hand?
Code:
import SwiftUI
import AppKit
struct Obj: Codable, Identifiable {
public var id: Int
public var name: String
public var language: String?
public var description: String?
public var stargazers_count: Int
public var forks_count: Int
}
class Fetch: ObservableObject {
#Published var results = [Obj]()
init(name: String) {
let url = URL(string: "https://api.github.com/users/"+name+"/repos?per_page=1000")!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode([Obj].self, from: data)
DispatchQueue.main.async {
self.results = results
}
print("Ok.")
} else {
print("No data.")
}
} catch {
print("Error:", error)
}
}.resume()
}
}
struct ContentViewBar: View {
#ObservedObject var fetch = Fetch(name: "github")
var body: some View {
VStack(alignment: .leading, spacing: 0) {
List(fetch.results) { el in
VStack(alignment: .leading, spacing: 0) {
Text("\(el.name) (\(el.stargazers_count)/\(el.forks_count)) \(el.language ?? "")").padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0))
if el.description != nil {
Text("\(el.description ?? "")")
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}.listStyle(SidebarListStyle())
// Button(action: { NSApplication.shared.terminate(self) }){
// Text("X")
// .font(.caption)
// .fontWeight(.semibold)
// }
// .padding(EdgeInsets(top: 2, leading: 0, bottom: 2, trailing: 2))
// .frame(width: 360.0, alignment: .trailing)
}
.padding(0)
.frame(width: 360.0, height: 360.0, alignment: .top)
}
}
struct ContentViewBar_Previews: PreviewProvider {
static var previews: some View {
ContentViewBar()
}
}
In the end, he seems to have succeeded.
Code:
List(fetch.results.sorted { $0.stargazers_count > $1.stargazers_count }) { el in
Sorting in UI drawing cycle might result in performance issue (especially in case of huge container), so it is better to perform sort either in-advance, or out-of-UI flow.
So I'd recommend to do it here
let results = try JSONDecoder().decode([Obj].self, from: data)
let sorted = results.sorted { $0.stargazers_count > $1.stargazers_count }
DispatchQueue.main.async {
self.results = sorted
}