Swift ui macos sort json by obj - json

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
}

Related

How to show attributed string from local json file data to SwiftUI detail view or WKWebView?

Currently, i've reached the place where i succeeded in parsing the local JSON data into SwiftUI's list-view. Now, the data i want to show to the user is formatted in html with various html tags combined with the main description. I want to show that description into WKWebView or some other view where i can show exactly html formatted text with html properties applied.
Below is the current scenario where i'm stuck.
when user presses the last drop down list, i want them to show that description on other view which can read and apply all the html formatted properties of the JSON data instead of just showing and other html tags.
Below is the code that i'm currently using..
import SwiftUI
struct lvl4: 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)
.fontWeight(.ultraLight)
.lineSpacing(20)
}
}
}
}
ForEach(book.bookContent) { bookContent in
VStack {
Text(bookContent.title)
.foregroundColor(Color.black)
.fontWeight(.heavy)
List(bookContent.child, children: \.child) { item in
Text(item.title)
.padding()
.monospacedDigit()
.drawingGroup()
}
}
}
}.navigationViewStyle(.stack)
.onAppear {
loadData()
}
}
// func replacingOccurrences(of target: String = "<p>",
// with replacement: String = "",
// options: NSString.CompareOptions = [],
// range searchRange: NSRange) -> String{
//
// return replacement
// }
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]
// var rendered: String
}
struct Child: Identifiable, Codable {
let id = UUID()
var title, type: String
var child: [Child]?
}
}
struct lvl4_Previews: PreviewProvider {
static var previews: some View {
lvl4()
}
}
New updated code with implemented disclosuregroup, outline group, content view: that has the
attributed string working and implemented disclosure group
import SwiftUI
import Foundation
struct ContentView: View {
#EnvironmentObject var booksList:BooksList
#State var books: [BookModel] = []
#State var selection: BookModel?
var body: some View {
// NavigationView {
VStack(alignment:.trailing, spacing: 40 ){
ScrollView(.vertical, showsIndicators: false){
ForEach(booksList.books) { book in
// NavigationLink(destination: lvl4(books: [book], selection: nil)){
// Text(book.bukTitle!)
// .listRowInsets(EdgeInsets())
//
if #available(iOS 15.0, *) {
DisclosureGroup ("\(Text(book.bukTitle!) .fontWeight(.medium) .font(.system(size: 27))) "){
ForEach(book.bookContent ?? []) { bookContent in
DisclosureGroup("\(Text(bookContent.title).fontWeight(.light) .font(.system(size: 25)))")
{
OutlineGroup(bookContent.child , children: \.child) { item in
if #available(iOS 15, *) {
Text(attributedString(from: item.title, font: Font.system(size: 23) ))
.navigationTitle(Text(bookContent.title))
.padding(10)
// if (([Child].self as? NSNull) == nil) {
// NavigationLink(destination: ScrollView {Text(attributedString(from: item.title, font: Font.system(size: 25) )).padding(30) .lineSpacing(10) .navigationTitle(Text(bookContent.title)) .navigationBarTitleDisplayMode(.inline)
//
// })
// {
//
// // EmptyView()
// // .navigationTitle(Text(bookContent.title))
// }
// }
}
}
}
}
}
}
}
}
}.padding(35)
}
//
// DisclosureGroup("\(Text(book.bukTitle!).fontWeight(.light) .font(.system(size: 23)))"){
//
// ForEach(book.bookContent ?? []) { bookContent in
//
// DisclosureGroup("\(Text(bookContent.title))" ){
//
// OutlineGroup(bookContent.child, children: \.child) { chld in
//
//
// List(bookContent.child, children: \.child)
// {
// OutlineGroup(bookContent.child, children: \.child) { item in
// if #available(iOS 15, *) {
//
// NavigationLink(destination: ScrollView{Text(attributedString(from: item.title, font: Font.system(size: 22) )).padding(30) .lineSpacing(10) .navigationTitle(Text(bookContent.title)) .navigationBarTitleDisplayMode(.inline)}){
// EmptyView()
//
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
// }
//}
#available(iOS 13.0.0, *)
struct ContentView_Previews: PreviewProvider {
#available(iOS 13.0.0, *)
static var previews: some View {
ContentView()
}
}
}
Issue 1 with implemented Navigationlink
: The updated code has Navigationlink commented out as the code is acting unusual..: The navigationLink starts to show in all the nested branches after first drop down. Below shown Picture dipcts the issue. ::
to "...show exactly html formatted text with html properties applied...", try this function that uses AttributedString:
(note, this is particular for this text type, Gujarati?)
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))
You can also look at this SO post/anwser: Convert html links inside text to clickable links - SwiftUI

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.

MacOS - Using JSON to format SwiftUI list with checkboxes (continued)

Yesterday, I asked a related question here.
I now have this script:
#available(OSX 11.0, *)
struct Mods_UI: View {
#State private var falseerror = false
#State var jsonDataList = [jsonData]()
var body: some View {
VStack {
List(jsonDataList, id: \.id) { jsonDataList in
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text(jsonDataList.display)
.font(.title3)
.fontWeight(.bold)
Text(String(jsonDataList.description))
.font(.subheadline)
}
Spacer()
Image(systemName: jsonDataList.enabled ?? false ? "checkmark.square": "square")
}
Spacer()
}
}
.onAppear(perform: loadData)
}
}
func loadData() {
guard let modsURL = URL(string: "https://raw.githubusercontent.com/nacrt/SkyblockClient-REPO/main/files/mods.json") else {
print("Invalid URL")
return
}
let task = URLSession.shared.dataTask(with: modsURL) { (data, _, error) in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode([jsonData].self, from: data!)
jsonDataList = result
print("Response:",jsonDataList)
} catch {
print(error)
}
}
task.resume()
}
}
#available(OSX 11.0, *)
struct Mods_UI_Previews: PreviewProvider {
static var previews: some View {
Mods_UI()
}
}
struct jsonData: Codable, Identifiable {
let id: String
let display: String
let description: String
let url: String?
let config: Bool?
let enabled: Bool?
let hidden: Bool?
let icon: String?
let categories: [String]?
}
I would like to format the checkbox: Image(systemName: jsonDataList.enabled ?? false ? "checkmark.square": "square") as a Toggle: Toggle("", isOn: $jsonDataList.enabled). I have tried a few ways of formatting this, and it always seems to return an error. My plan for this is to be able to check if the box is checked, and if so, install a file somewhere.
Another thing that I'd like to do is only show the items in the JSON that either have jsonDataList.hidden as false or just do not have jsonDataList.hidden.
There are a couple of things that need to happen in order to make this work. The first is that your model should probably have enabled be a var instead of a let -- that way, its value can be changed when the Toggle is manipulated:
struct jsonData: Codable, Identifiable {
let id: String
let display: String
let description: String
let url: String?
let config: Bool?
var enabled: Bool? //<-- Here
let hidden: Bool?
let icon: String?
let categories: [String]?
}
Next, you'll need to create a Binding to use with the Toggle. Because your model is in an array, the Binding will need to know which item to update (its index in the array). You'll see that in my code as the enabledBindingForIndex function.
Lastly, in order to get the index for each item that you'll be passing into the enabledBindingForIndex function, the ForEach code will have to be changed a little bit so that it passes in both the JSON item and the index. I like using .enumerated() for this. Note that the id of the item is now .1.id because you'll be getting a tuple with both the index (.0) and the item (.1). I also filter out the hidden items in that same line.
struct Mods_UI: View {
#State private var falseerror = false
#State var jsonDataList = [jsonData]()
func enabledBindingForIndex(index: Int) -> Binding<Bool> {
Binding<Bool> { () -> Bool in
return jsonDataList[index].enabled ?? false
} set: { (newValue) in
jsonDataList[index].enabled = newValue
}
}
var body: some View {
VStack {
List(Array(jsonDataList.filter { $0.hidden != true }.enumerated()), //<-- Here
id: \.1.id) { (index,jsonDataList) in //<-- Here
VStack(alignment: .leading) {
HStack {
VStack(alignment: .leading) {
Text(jsonDataList.display)
.font(.title3)
.fontWeight(.bold)
Text(String(jsonDataList.description))
.font(.subheadline)
}
Spacer()
Toggle(isOn: enabledBindingForIndex(index: index)) { } //Here
}
Spacer()
}
}
.onAppear(perform: loadData)
}
}
func loadData() {
guard let modsURL = URL(string: "https://raw.githubusercontent.com/nacrt/SkyblockClient-REPO/main/files/mods.json") else {
print("Invalid URL")
return
}
let task = URLSession.shared.dataTask(with: modsURL) { (data, _, error) in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode([jsonData].self, from: data!)
jsonDataList = result
print("Response:",jsonDataList)
} catch {
print(error)
}
}
task.resume()
}
}
If you wanted to have some side effect that takes place when the Toggle is enabled/disabled, you could do so in the enabledBindingForIndex set closure.

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()
}
}

Toggle sorting in List View

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()
}
}
}