diff --git a/Development/Demo/MyBook.swift b/Development/Demo/MyBook.swift index 0d5de45..9a52c6e 100644 --- a/Development/Demo/MyBook.swift +++ b/Development/Demo/MyBook.swift @@ -19,12 +19,57 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - import Foundation - import StorybookKit import SwiftUISupport +@MainActor +let myBook2 = BookContainer( + store: .init( + title: "MyUI", + links: { + + BookNavigationLink(title: "Preview UI") { + BookSection(title: "Section") { + + Text( + """ + Something description about this section. + """ + ) + + BookPreview { _ in + let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 80)) + view.backgroundColor = .systemPurple + NSLayoutConstraint.activate([ + view.widthAnchor.constraint(equalToConstant: 80), + view.heightAnchor.constraint(equalToConstant: 80), + ]) + return view + } + + BookPreview(title: "A component") { _ in + let view = UIView(frame: .null) + view.backgroundColor = .systemPurple + return view + } + .previewFrame(width: 80, height: 80) + + BookPreview { _ in + let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 80)) + view.backgroundColor = .systemPurple + return view + } + .previewFrame(maxWidth: .greatestFiniteMagnitude, idealHeight: 10) + + } + + } + + } + ) +) + let myBook: some BookType = Book(title: "MyUI") { Text("MyBook") @@ -36,27 +81,34 @@ let myBook: some BookType = Book(title: "MyUI") { BookNavigationLink(title: "Preview UI") { BookPage(title: "Typography") { - Text(""" -Here is `BookHeadline`, It allows us to describe something big picture. -""") - - Text(""" -Here is `BookParagpraph` -It allows us to display multiple lines. + Text( + """ + Here is `BookHeadline`, It allows us to describe something big picture. + """ + ) -Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, -""") + Text( + """ + Here is `BookParagpraph` + It allows us to display multiple lines. - Text(""" -Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, -""") + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + """ + ) + Text( + """ + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, + """ + ) BookSection(title: "Section") { - Text(""" -Something description about this section. -""") + Text( + """ + Something description about this section. + """ + ) BookPreview { _ in let view = UIView(frame: .init(x: 0, y: 0, width: 80, height: 80)) @@ -81,7 +133,7 @@ Something description about this section. return view } .previewFrame(maxWidth: .greatestFiniteMagnitude, idealHeight: 10) - + } BookSection(title: "Section") { @@ -167,10 +219,10 @@ Something description about this section. } } - + } -fileprivate func labelExpandingTestBook() -> some View { +private func labelExpandingTestBook() -> some View { BookSection(title: "UILabel updating text") { BookPreview { context in diff --git a/Development/Demo/RootView.swift b/Development/Demo/RootView.swift index d481680..613d092 100644 --- a/Development/Demo/RootView.swift +++ b/Development/Demo/RootView.swift @@ -29,10 +29,12 @@ struct RootView: View { var body: some View { VStack { - StorybookDisplayRootView( - book: myBook - ) +// StorybookDisplayRootView( +// book: myBook +// ) + StorybookDisplayRootView2(book: myBook2) + } } } diff --git a/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ae6c658..f0702c3 100644 --- a/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Development/Storybook.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "version" : "0.10.0" } }, + { + "identity" : "resultbuilderkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/ResultBuilderKit", + "state" : { + "revision" : "34aa57fcee5ec3ee0f368b05cb53c7919c188ab2", + "version" : "1.3.0" + } + }, { "identity" : "swiftui-gesture-velocity", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index f29681a..cc8bb24 100644 --- a/Package.swift +++ b/Package.swift @@ -15,12 +15,14 @@ let package = Package( .package(url: "https://github.com/FluidGroup/TextureBridging.git", branch: "main"), .package(url: "https://github.com/FluidGroup/TextureSwiftSupport.git", branch: "main"), .package(url: "https://github.com/FluidGroup/swiftui-support", from: "0.4.1"), + .package(url: "https://github.com/FluidGroup/ResultBuilderKit", from: "1.3.0") ], targets: [ .target( name: "StorybookKit", dependencies: [ - .product(name: "SwiftUISupport", package: "swiftui-support") + .product(name: "SwiftUISupport", package: "swiftui-support"), + "ResultBuilderKit" ] ), .target( diff --git a/Sources/StorybookKit/Primitives/Book.swift b/Sources/StorybookKit/Primitives/Book.swift index 196cd37..730e69d 100644 --- a/Sources/StorybookKit/Primitives/Book.swift +++ b/Sources/StorybookKit/Primitives/Book.swift @@ -19,6 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. +import ResultBuilderKit import SwiftUI public protocol BookType: View { @@ -45,23 +46,114 @@ public struct Book: BookType { } } .navigationTitle(title) - .environment(\.bookContext, BookContext()) } } +public final class BookStore: ObservableObject { + + @Published var historyLinks: [BookNavigationLink] = [] + + public let title: String + public let links: [BookNavigationLink] + + private let userDefaults = UserDefaults(suiteName: "jp.eure.storybook2") ?? .standard + + public init( + title: String, + @ArrayBuilder links: () -> [BookNavigationLink] + ) { + self.title = title + self.links = links().sorted(by: { $0.title < $1.title }) + + updateHistory() + } + + private func updateHistory() { + + let indexes = userDefaults.array(forKey: "history") as? [Int] ?? [] + + let _links = indexes.compactMap { index -> BookNavigationLink? in + guard let link = links.first(where: { $0.declarationIdentifier.index == index }) else { + return nil + } + return link + } + + historyLinks = _links + + } + + func onOpen(link: BookNavigationLink) { + + let index = link.declarationIdentifier.index + + var current = userDefaults.array(forKey: "history") as? [Int] ?? [] + if let index = current.firstIndex(of: index) { + current.remove(at: index) + } + current.insert(index, at: 0) + current = Array(current.prefix(5)) + + userDefaults.set(current, forKey: "history") + + updateHistory() + } + +} + +public struct BookContainer: BookType { + + @ObservedObject var store: BookStore + + @MainActor + public init( + store: BookStore + ) { + self.store = store + } + + public var body: some View { + NavigationView { + List { + Section { + ForEach(store.historyLinks) { link in + link + } + } header: { + Text("History") + } + Section { + ForEach(store.links) { link in + link + } + } header: { + Text("Contents") + } + } + .listStyle(.grouped) + } + .navigationTitle(store.title) + .environment(\.bookContext, store) + } + +} + public final class BookContext { + func onOpen(link: BookNavigationLink) { + print(link) + } } private enum BookContextKey: EnvironmentKey { - static var defaultValue: BookContext? + static var defaultValue: BookStore? } extension EnvironmentValues { - public var bookContext: BookContext? { + public var bookContext: BookStore? { get { self[BookContextKey.self] } diff --git a/Sources/StorybookKit/Primitives/BookNavigationLink.swift b/Sources/StorybookKit/Primitives/BookNavigationLink.swift index e88c2a3..ed314df 100644 --- a/Sources/StorybookKit/Primitives/BookNavigationLink.swift +++ b/Sources/StorybookKit/Primitives/BookNavigationLink.swift @@ -43,18 +43,24 @@ private func issueUniqueNumber() -> Int { } /// A component that displays a disclosure view. -public struct BookNavigationLink: BookView { +public struct BookNavigationLink: BookView, Identifiable { + + @Environment(\.bookContext) var context + + public var id: DeclarationIdentifier { + declarationIdentifier + } public let title: String - public let destination: Destination + public let destination: AnyView public let declarationIdentifier: DeclarationIdentifier - public init( + public init( title: String, @ViewBuilder destination: () -> Destination ) { self.title = title - self.destination = destination() + self.destination = AnyView(destination()) self.declarationIdentifier = .init() } @@ -64,12 +70,14 @@ public struct BookNavigationLink: BookView { title, destination: { GeometryReader(content: { geometry in - ScrollView { destination .frame(width: geometry.size.width) } }) + .onAppear(perform: { + context?.onOpen(link: self) + }) } ) diff --git a/Sources/StorybookKit/StorybookDisplayRootView.swift b/Sources/StorybookKit/StorybookDisplayRootView.swift index b536631..821307f 100644 --- a/Sources/StorybookKit/StorybookDisplayRootView.swift +++ b/Sources/StorybookKit/StorybookDisplayRootView.swift @@ -20,6 +20,25 @@ public struct StorybookDisplayRootView: View { } } +public struct StorybookDisplayRootView2: View { + + public let book: BookContainer + + public init(book: BookContainer) { + self.book = book + } + + public var body: some View { + + _ViewControllerHost { + let controller = _ViewController(book: book) + return controller + } + + } +} + + final class _ViewController: UIViewController { private let book: Book