我正在使用的结构:
import SwiftUI
struct lvl4: View {
// @State var book: Book = Book()
@State var books: [BookModel] = []
@State var selection: BookModel?
//ios 14 must to get the syntex right..
@available(iOS 14, *)
var body: some View {
NavigationView {
List(books) { book in
ForEach(book.bookContent ?? []) { bookContent in
Section(header: Text(bookContent.title).font(.largeTitle) .fontWeight(.heavy)) {
OutlineGroup(bookContent.child, children: \.child) { item in
if #available(iOS 15, *) {
Text(attributedString(from: item.title, font: Font.system(size: 20)))
.navigationBarTitle(book.bukTitle!)
}
}
}
}
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(StackNavigationViewStyle())
//
.listStyle(SidebarListStyle())
// .navigationViewStyle(.stack)
.onAppear {
//loadData()
}
}
{
}
struct Buk: Identifiable, Codable {
let id = UUID()
var bukTitle: String = ""
var isLive: Bool = false
var userCanCopy: Bool = false
var bookContent: [BookContent] = []
enum CodingKeys: String, CodingKey {
case bukTitle = "book_title"
case isLive = "is_live"
case userCanCopy = "user_can_copy"
case bookContent = "book_content"
}
}
struct BookContent: Identifiable, Codable {
let id = UUID()
var type,title:String
var child: [Child]
}
struct Child: Identifiable, Codable {
let id = UUID()
var type,title:String
var child: [Child]?
}
@available(iOS 15, *)
func attributedString(from str: String, font: Font) -> AttributedString {
if let theData = str.data(using: .utf16) {
do {
let theString = try NSAttributedString(data: theData, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
var attaString = AttributedString(theString)
attaString.font = font
return attaString
} catch {
print("\(error)")
}
}
return AttributedString(str)
}
struct lvl4_Previews: PreviewProvider {
static var previews: some View {
lvl4()
}
}
现状:
在同一个V堆栈上显示的所有本地存储的书籍(json文件)都有一个列表。那些书是从本地目录中取来的。现在,当用户按下显示组/大纲组的下拉时。用于解析json的"Struct"(即lvl4())显示了嵌套的json数据。
当前数据如下所示,实现了大纲组/公开组。

我的尝试 ::
显示所有图书都列在同一视图上的书籍。但无法在另一个视图上显示来自json的最后描述子。下面是我尝试使用“披露”组的代码。公开组中使用的大纲组使用子元素:.child,这有助于使嵌套数据在视图上显示。最后一个嵌套数据将显示,直到json的编码键为“子”:null,直到这里:
json数据的一小部分(同一行中数百个相似的嵌套数据):
[
{
"book_title": "સત્સંગિજીવન સાગર મંથન",
"is_live": false,
"user_can_copy": true,
"book_content": [
{
"title": "સત્સંગિજીવન માહાત્મ્ય",
"type": "title",
"child": [
{
"title": "૦૧. પૂર્વભૂમિકા",
"type": "title",
"child": [
{
"title": "૦૧. મંગલાચરણ",
"type": "title",
"child": [
{
"title": "<p>સતતં નિજમૂર્તિ ચિન્તકાનામ્, અધિક શ્વેત મનોહર પ્રકાશે ।<br />હૃદિ દર્શિત રમ્ય દિવ્યરૂપં, ભગવન્તં તમહં હરિં નમામિ ।।</p><h3 style='text-align: center;'><strong>( </strong><strong>અર્થ</strong><strong> )</strong></h3><p style='text-align: justify;'> “હંમેશાં પોતાની મૂર્તિનું ચિંતન કરનારા, ભક્તજનોના હૃદય કમળમાં જણાતા અત્યંત શ્વેત મનોહર પ્રકાશવાળા, અક્ષર બ્રહ્મમાં જેમણે બતાવ્યું છે દિવ્યરૂપ એવા ભગવાન શ્રીહરિને હું નમસ્કાર કરું છું.”</p><p style='text-align: justify;'> અનંતકોટિ બ્રહ્માંડોના ઉત્પત્તિના કારણ તથા અનંત ઐશ્વર્ય યુક્ત એવા પૂર્ણપુરુષોત્તમ શ્રી સ્વામિનારાયણ મહાપ્રભુજી તથા આપણા (ઉદ્ધવ) સંપ્રદાયના આદ્ય સ્થાપક ઉદ્ધવાવતાર શ્રી રામાનંદસ્વામી તથા જેની શિષ્ય પરંપરાગતમાં મને શિષ્ય બનવાનો સુલભ અવસર પ્રાપ્ત થયો છે, જેઓને ખુદ સ્વામિનારાયણ ભગવાન ગુરુ તરીકે માનીને મર્યાદા રાખતા અને જેઓને સત્સંગની 'મા' તરીકેનું બિરુદ આપી શ્રીહરિજીએ બહુમાન કર્યું હતું, એવા સર્વગુણે સંપન્ન મારા આદિ ગુરુ સદ્ગુરુ શ્રી મુક્તાનંદ સ્વામી તથા મૂળ અક્ષરમૂર્તિ યોગીરાજ સદ્ગુરુ ગોપાળાનંદ સ્વામી તથા જેઓને ખુદ શ્રીજી મહારાજે પોતાને સ્થાને બેસાડી સંપ્રદાયની ધુરા સોંપી આચાર્યપદ અર્પણ કર્યું છે એવા, સંતોનો અપાર મહિમા સમજનારા અને ગૃહસ્થાશ્રમમાં હોવા છતાં નિષ્કામી વ્રતને ધારણ કરનાર એવા પ. પૂ. ધ. ધુ. ૧૦૦૮ આચાર્ય શ્રી રઘુવીરજી મહારાજ તથા ધ્યાનના અંગવાળા અને આત્મનિષ્ઠાને સાંગોપાંગ જીવનમાં ઉતારનારા પ.પૂ.ધ.ધુ.૧૦૦૮ આચાર્યશ્રી અયોધ્યાપ્રસાદજી મહારાજ તથા સર્વે મહાન સંતો અને મહાન ભક્તોના ચરણોમાં વંદના કરી 'ગ્રંથરાજ શ્રીમદ્ સત્સંગિજીવન' માંથી મારી અલ્પમતિ અનુસાર મંથન કરી સાર રૂપ ઘી શોધવા માટે જઇ રહ્યો છું.</p>",
"type": "content",
"child": null
}
]
}
]
}
]
}
]
}
]
当前数据如下所示,实现了公开组。
所用代码如下:
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{
List(booksList.books) { book in
// NavigationLink(destination: lvl4(books: [book], selection: nil)){
// Text(book.bukTitle!)
//
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))
// 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))
// }
// }
}
}
}
}
}
} else {
// Fallback on earlier versions
}
// }
}
}
}
//
// 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()
}
}
}
struct在struct lvl4中,这是上面在第一段代码中给出的。
BookModel代码如下:
import Foundation
enum BookParseError: Error {
case bookParsingFailed
}
struct BookModelForJSONConversion: Codable {
var id:Int
var title: String?
var content: [BookContent]?
func convertToJsonString()->String?{
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
var encodedString:String?
do {
let encodePerson = try jsonEncoder.encode(self)
let endcodeStringPerson = String(data: encodePerson, encoding: .utf8)!
//print(endcodeStringPerson)
encodedString = endcodeStringPerson
} catch {
print(error.localizedDescription)
return nil
}
return encodedString
}
}
struct BookModel: Identifiable, Codable {
var id:Int
var bukTitle: String?
var isLive: Bool?
var userCanCopy: Bool?
var bookContent: [BookContent]?
enum CodingKeys: String, CodingKey {
case id = "id"
case bukTitle = "title"
case isLive = "is_live"
case userCanCopy = "user_can_copy"
case bookContent = "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]?
//}
enum BooksDirectory {
/// Default, system Documents directory, for persisting media files for upload.
case downloads
/// Returns the directory URL for the directory type.
///
fileprivate var url: URL {
let fileManager = FileManager.default
// Get a parent directory, based on the type.
let parentDirectory: URL
switch self {
case .downloads:
parentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
}
return parentDirectory.appendingPathComponent(VBBooksManager.booksDirectoryName, isDirectory: true)
}
}
class VBBooksManager:NSObject {
fileprivate static let booksDirectoryName = "books"
let directory: BooksDirectory
@objc (defaultManager)
static let `default`: VBBooksManager = {
return VBBooksManager()
}()
// MARK: - Init
/// Init with default directory of .uploads.
///
/// - Note: This is particularly because the original Media directory was in the NSFileManager's documents directory.
/// We shouldn't change this default directory lightly as older versions of the app may rely on Media files being in
/// the documents directory for upload.
///
init(directory: BooksDirectory = .downloads) {
self.directory = directory
}
// MARK: - Instance methods
/// Returns filesystem URL for the local Media directory.
///
@objc func directoryURL() throws -> URL {
let fileManager = FileManager.default
let mediaDirectory = directory.url
// Check whether or not the file path exists for the Media directory.
// If the filepath does not exist, or if the filepath does exist but it is not a directory, try creating the directory.
// Note: This way, if unexpectedly a file exists but it is not a dir, an error will throw when trying to create the dir.
var isDirectory: ObjCBool = false
if fileManager.fileExists(atPath: mediaDirectory.path, isDirectory: &isDirectory) == false || isDirectory.boolValue == false {
try fileManager.createDirectory(at: mediaDirectory, withIntermediateDirectories: true, attributes: nil)
}
return mediaDirectory
}
func saveBook(bookName:String,bookData:String)->Error?{
//TODO: Save book into Document directory
do {
var finalBookName = bookName
if !finalBookName.contains(".json"){
finalBookName = "\(bookName).json"
}
let bookPath = try? self.directoryURL().appendingPathComponent(finalBookName)
print(bookPath?.relativePath)
do {
let fileManager = FileManager.default
if fileManager.fileExists(atPath: bookPath!.relativePath){
try fileManager.removeItem(at: bookPath!)
}
let data = Data(bookData.utf8)
try? data.write(to: bookPath!, options: .atomic)
//Just for Testing purpose call load book
//lodBook(bookName: finalBookName)
}
catch let error as NSError {
print(error)
return error
}
}
catch let error as NSError{
print(error)
return error
}
return nil
//fileManager.wri wr(bookPath.relativePath, contents: Data(bookData), attributes: nil)
}
//https://stackoverflow.com/questions/39415249/best-practice-for-swift-methods-that-can-return-or-error
func loadBookFromDocumentDirectory(bookName:String) throws -> BookModel? {
let fileManager = FileManager.default
do {
var finalBookName = bookName
if !finalBookName.contains(".json"){
finalBookName = "\(bookName).json"
}
let bookPath = try? self.directoryURL().appendingPathComponent(finalBookName)
print(bookPath?.relativePath)
do {
if fileManager.fileExists(atPath: bookPath!.relativePath){
let jsonBookString = fileManager.contents(atPath: bookPath!.relativePath)
do {
let data = try Data(jsonBookString!)
guard let parsedBookObject:BookModel? = try JSONDecoder().decode(BookModel.self, from: data) else {
throw BookParseError.bookParsingFailed
}
return parsedBookObject ?? nil
//print(parsedBookObject)
}
catch let error as NSError{
print("error: \(error)")
throw error
}
}else{
}
}
catch let error as NSError {
print(error)
throw error
}
}
catch let error as NSError{
print(error)
throw error
}
return nil
}
func loadAllSavedBooks()->[BookModel]?{
var allBooks:[BookModel] = []
let fileManager = FileManager.default
guard let booksPath = try? self.directoryURL() else {
return []
}
print(booksPath)
do {
// Get the directory contents urls (including subfolders urls)
let directoryContents = try fileManager.contentsOfDirectory(at: booksPath, includingPropertiesForKeys: nil)
print(directoryContents)
// if you want to filter the directory contents you can do like this:
let books = directoryContents.filter{ $0.pathExtension == "json" }
let bookNames = books.map{ $0.deletingPathExtension().lastPathComponent }
print("bookNames list:", bookNames)
//TODO: Load all the books and send array back
for bookName in bookNames {
do {
let book = try loadBookFromDocumentDirectory(bookName:bookName)
allBooks.append(book!)
} catch BookParseError.bookParsingFailed {
continue
}
}
return allBooks
} catch let error as NSError {
print(error)
}
return allBooks
}
}
发布::
问题1: --如果我尝试使用导航链接(注释掉了),那么它将在Disclosure组的所有子程序中显示导航链接,而不是仅显示获得最后一个子节点的最后一个子(“子”:null)。
发布于 2022-02-09 13:41:43
这需要一段时间来解析。对于下一个问题,请从代码中提取问题所不需要的所有内容,这样更容易理解。
有了你的模型,你犯的第一个错误就是做任何选择。这就给了你一个不必要的复杂程度。您最关心的是处理Child的数组,但是如果数组不是空的,您只需要处理它们。如果你让它们是可选的,你就不得不打开它们,然后看看它们是否是空的。这是不必要的。
此外,就数据模型而言,是一个BookContent == Child。完全没有理由两者兼得,所以我放弃了Child。
对JSON进行重新建模,以便每个节点都有一个值,即使它只是一个空数组或"“字符串。因为您控制了JSON,所以保持简单。
如您所见,我递归地呈现了每个BookContent的视图,因为每个BookContent都有一个[BookContent]。如果[BookContent]为空,则递归结束。
您的意见:
struct ContentView: View {
@State var booksList: [BookModel] = [
BookModel(id: 1, bukTitle: "Book Title", isLive: false, userCanCopy: false, bookContent: [
BookContent(title: "Content Title", type: "", children: [
BookContent(title: "2nd Level Book Content", type: "", children: [
BookContent(title: "3rd Level Book Content", type: "", children: [
BookContent(title: "4th Level Book Content", type: "", children: [])
])
])
])
])]
var body: some View {
NavigationView {
VStack{
List(booksList) { book in
Text(book.bukTitle)
ForEach(book.bookContent) { bookContent in
BookContentView(bookContent: bookContent)
}
}
}
}
}
}
struct BookContentView: View {
let bookContent: BookContent
var body: some View {
Text(bookContent.title)
ForEach(bookContent.children) { bookContent in
BookContentView(bookContent: bookContent)
}
}
}你的模特:
struct BookModel: Identifiable, Codable {
var id:Int
var bukTitle: String
var isLive: Bool
var userCanCopy: Bool
var bookContent: [BookContent]
enum CodingKeys: String, CodingKey {
case id = "id"
case bukTitle = "title"
case isLive = "is_live"
case userCanCopy = "user_can_copy"
case bookContent = "content"
}
}
struct BookContent: Identifiable, Codable {
let id = UUID()
var title, type: String
var children: [BookContent]
// Since your id is a let constant, adding CodingKeys without id
// silences the Codable warning that id won't be coded.
enum CodingKeys: String, CodingKey {
case title = "title"
case type = "type"
case children = "child"
}
}在一个单独的应用程序中使用这段代码,然后按需要重新使用您的代码。
https://stackoverflow.com/questions/70985347
复制相似问题