我想把这张图片展示在这本书的封面,即披露视图旁边。图像来自结构,该结构用于调用json嵌套数据,以与图像一起显示在公开组中。JSON文件保存在本地,调用这些文件将其显示在disclosuregroup中。
我所尝试的问题::
用于实现上述图像的代码:
import Foundation
import SwiftUI
import UIKit
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
//Alternative way , each book on seperate view
//NavigationLink(destination: lvl4(books: [book], selection: nil)){
// Text(book.bukTitle!)
//
if #available(iOS 15.0, *) {
if #available(iOS 15, *){
label: do {
AsyncImage(url: URL(string: "\(book.coverImage!)")) .scaledToFit() .fixedSize(horizontal:true, vertical: true) .frame(minWidth: 10, maxWidth: 20)
}
}
DisclosureGroup( "\(Text(book.bukTitle!) .fontWeight(.medium) .font(.system(size: 30)))"
) {
ForEach(book.bookContent ?? []) { bookContent in
DisclosureGroup(
"\(Text(bookContent.title).fontWeight(.medium) .font(.system(size: 25)))"
) {
OutlineGroup(bookContent.child, children: \.child) { item in
NavigationLink {
if #available(iOS 15, *) {
ScrollView {
Text(attributedString(from: item.title, font: Font.system(size: 25)))
.padding(30).lineSpacing(10).navigationTitle(Text(bookContent.title))
.navigationBarTitleDisplayMode(.inline)
}.frame(minWidth: 0, maxWidth: .infinity)
.background(Color(UIColor.hexStringToUIColor(hexStr: ("efe8d2"))))
}
} label: {
if #available(iOS 15, *) {
Text(
"\(Text(attributedString(from: item.title, font: Font.system(size: 23) )))"
)
.lineLimit(1).lineSpacing(20)
} else {
// Fallback on earlier versions
}
}
.disabled(item.child != nil).lineSpacing(20)
}.lineSpacing(20)
}.lineSpacing(20)
}
}
}
}.lineSpacing(20)
}.lineSpacing(20)
}.padding(35).lineSpacing(20)
}
import Foundation
class BooksList: ObservableObject {
@Published var books:[BookModel]
init() {
let booksManager = VBBooksManager()
books = booksManager.loadAllSavedBooks() ?? []
}
}
以下代码用于BookModel。
import Foundation
enum BookParseError: Error {
case bookParsingFailed
}
struct BookModelForJSONConversion: Codable {
var id:Int
var title: String?
var content: [BookContent]?
var bookCoverImage:String?
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]?
var coverImage:String?
enum CodingKeys: String, CodingKey {
case id = "id"
case bukTitle = "title"
case isLive = "is_live"
case userCanCopy = "user_can_copy"
case bookContent = "content"
case coverImage = "bookCoverImage"
}
}
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]?
}
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"
}
}
@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 // <-- here
return attaString
} catch {
print("\(error)")
}
}
return AttributedString(str)
}
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
// let bookModel = try loadBookFromDocumentDirectory(bookName: finalBookName)
// print(bookModel?.coverImage)
}
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
}
}
我正在努力实现的目标:
编辑包含的注释以更好地解释代码并显示图片
您可能以一种奇怪的方式嵌套For每一个
、NavigationLink
和Stacks。
尝试此操作并沿着View添加您的特定代码,包括您的模型属性和AsyncImage。格式如您所愿。结果应该是您正在寻找的。
struct Example: View {
@State private var bookList: [Book]
init() {
let book1 = Book(image: "book", content: ["Line 1", "Line 2"])
let book2 = Book(image: "book.closed.fill", content: ["Line A", "Line B", "Line C"])
bookList = [book1, book2]
}
var body: some View {
// "NavigationView" is necessary to allow moving from the list to the details
NavigationView {
VStack {
// Title before the list
Text("Total books: \(bookList.count)")
// "List" will provide the items one after the other
List(bookList) { book in
// Here is what you see for each single item in the list
HStack {
// "NavigationLink: is how you see the details by tapping on the item
NavigationLink {
// This is the detail view
ScrollView {
Text("First line")
Text("Second line")
Text("Third line")
Text("Fourth line")
}
} label: {
// This is the item view (how you see each item in the list)
HStack {
// Showing an Image
Image(systemName: book.image)
// On the side of the image, show another list (without navigation)
VStack {
ForEach(book.content, id: \.self) { bookContent in
Text(bookContent)
}
}
}
}
}
.padding()
// This is just for decoration
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 2)
.foregroundColor(.green)
}
.listRowSeparator(.hidden)
}
.listStyle(.plain)
}
}
}
}
这是您看到的列表:
点击列表中的某一项时,您可以通过以下方式查看详细信息: