我正在使用的結構:
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()
}
}
現在的情況:
有一個串列,列出了所有本地存盤的書籍(json 檔案),它們顯示在同一個 V 堆疊上。這些書是從本地目錄中獲取的。現在,當用戶按下公開組/大綱組的下拉選單時。用于決議 json 的“Struct”(即 lvl4())顯示嵌套的 json 資料。
通過實施大綱組/披露組,當前資料如下所示。

我的嘗試::
在列出所有書籍的同一視圖上顯示書籍。但無法在另一個視圖上顯示來自 json 的最后一個描述子項。以下是我嘗試使用 Disclosure 組的代碼。在公開組中使用的大綱組使用 children: .child 有助于使嵌套資料在視圖上顯示。顯示最后的嵌套資料,直到 json 的編碼鍵為 "child": 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
}
]
}
]
}
]
}
]
}
]
通過實施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{
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 Child 在上面第一個代碼中給出的 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 組的所有子項中顯示導航鏈接,而不是僅在獲得最后一個子項的最后一個子項中顯示導航鏈接(“child”:null) .
uj5u.com熱心網友回復:
這需要一些時間來決議。對于下一個問題,請從代碼中提取問題不需要的所有內容,以便更容易理解。
使用您的模型時,您犯的第一個錯誤就是將所有內容都設為可選。這給您帶來了不必要的復雜性。您最關心的是處理 的陣列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://www.uj5u.com/ruanti/427934.html
