diff --git a/Development/Development.xcodeproj/project.pbxproj b/Development/Development.xcodeproj/project.pbxproj index eb00bf0..96c808d 100644 --- a/Development/Development.xcodeproj/project.pbxproj +++ b/Development/Development.xcodeproj/project.pbxproj @@ -14,7 +14,8 @@ 4B3722682A33C701005FF24A /* BookUIKitBasedCompositional.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3722672A33C701005FF24A /* BookUIKitBasedCompositional.swift */; }; 4B910EBC2A77A2F50079D26D /* BookUIKitBasedFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B910EBB2A77A2F50079D26D /* BookUIKitBasedFlow.swift */; }; 4B9981D82A34F9B500840751 /* CompositionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 4B9981D72A34F9B500840751 /* CompositionKit */; }; - 4BCF78712CB7E07C0081171E /* VersatileList in Frameworks */ = {isa = PBXBuildFile; productRef = 4BCF78702CB7E07C0081171E /* VersatileList */; }; + 4BC34FAF2CDB1B9200D22811 /* CollectionView in Frameworks */ = {isa = PBXBuildFile; productRef = 4BC34FAE2CDB1B9200D22811 /* CollectionView */; }; + 4BC34FB12CDB1C0C00D22811 /* BookCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC34FB02CDB1C0500D22811 /* BookCollectionView.swift */; }; 4BD04C152B2C05C600FE41D9 /* BookPlainCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD04C142B2C05C600FE41D9 /* BookPlainCollectionView.swift */; }; 4BD04C172B2C13BB00FE41D9 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD04C162B2C13BB00FE41D9 /* Logger.swift */; }; 4BD04C192B2C15E100FE41D9 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD04C182B2C15E100FE41D9 /* Color.swift */; }; @@ -46,6 +47,7 @@ 4B26A68A2A3323D400B75FB4 /* swift-dynamic-list */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "swift-dynamic-list"; path = ..; sourceTree = ""; }; 4B3722672A33C701005FF24A /* BookUIKitBasedCompositional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookUIKitBasedCompositional.swift; sourceTree = ""; }; 4B910EBB2A77A2F50079D26D /* BookUIKitBasedFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookUIKitBasedFlow.swift; sourceTree = ""; }; + 4BC34FB02CDB1C0500D22811 /* BookCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookCollectionView.swift; sourceTree = ""; }; 4BD04C142B2C05C600FE41D9 /* BookPlainCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookPlainCollectionView.swift; sourceTree = ""; }; 4BD04C162B2C13BB00FE41D9 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 4BD04C182B2C15E100FE41D9 /* Color.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; @@ -57,10 +59,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 4BC34FAF2CDB1B9200D22811 /* CollectionView in Frameworks */, 4BEAFA4E2A3CE48800478C59 /* AsyncMultiplexImage-Nuke in Frameworks */, 4BEAFA4C2A3CE48800478C59 /* AsyncMultiplexImage in Frameworks */, 4B9981D82A34F9B500840751 /* CompositionKit in Frameworks */, - 4BCF78712CB7E07C0081171E /* VersatileList in Frameworks */, 4BD542E92A362B40006261B9 /* DynamicList in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -89,6 +91,7 @@ 4B26A6792A33239500B75FB4 /* Development */ = { isa = PBXGroup; children = ( + 4BC34FB02CDB1C0500D22811 /* BookCollectionView.swift */, 4B26A67A2A33239500B75FB4 /* DevelopmentApp.swift */, 4B26A67C2A33239500B75FB4 /* ContentView.swift */, 4B26A67E2A33239600B75FB4 /* Assets.xcassets */, @@ -140,7 +143,7 @@ 4BD542E82A362B40006261B9 /* DynamicList */, 4BEAFA4B2A3CE48800478C59 /* AsyncMultiplexImage */, 4BEAFA4D2A3CE48800478C59 /* AsyncMultiplexImage-Nuke */, - 4BCF78702CB7E07C0081171E /* VersatileList */, + 4BC34FAE2CDB1B9200D22811 /* CollectionView */, ); productName = Development; productReference = 4B26A6772A33239500B75FB4 /* Development.app */; @@ -206,6 +209,7 @@ 4B3722682A33C701005FF24A /* BookUIKitBasedCompositional.swift in Sources */, 4BD04C172B2C13BB00FE41D9 /* Logger.swift in Sources */, 4BD04C192B2C15E100FE41D9 /* Color.swift in Sources */, + 4BC34FB12CDB1C0C00D22811 /* BookCollectionView.swift in Sources */, 4BD04C152B2C05C600FE41D9 /* BookPlainCollectionView.swift in Sources */, 4B910EBC2A77A2F50079D26D /* BookUIKitBasedFlow.swift in Sources */, ); @@ -436,9 +440,9 @@ package = 4B9981D62A34F9B500840751 /* XCRemoteSwiftPackageReference "CompositionKit" */; productName = CompositionKit; }; - 4BCF78702CB7E07C0081171E /* VersatileList */ = { + 4BC34FAE2CDB1B9200D22811 /* CollectionView */ = { isa = XCSwiftPackageProductDependency; - productName = VersatileList; + productName = CollectionView; }; 4BD542E82A362B40006261B9 /* DynamicList */ = { isa = XCSwiftPackageProductDependency; diff --git a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3aac905..ec38286 100644 --- a/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Development/Development.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "e89878bf140720e29e318b93f44c5d0df6cceb734010ef6111491735cb630699", "pins" : [ { "identity" : "compositionkit", @@ -36,6 +37,15 @@ "version" : "12.1.0" } }, + { + "identity" : "swift-indexed-collection", + "kind" : "remoteSourceControl", + "location" : "https://github.com/FluidGroup/swift-indexed-collection", + "state" : { + "revision" : "9b17bf06eae73fee93dae9a0fa6de2e33900d9c5", + "version" : "0.2.1" + } + }, { "identity" : "swiftui-async-multiplex-image", "kind" : "remoteSourceControl", @@ -64,5 +74,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/Development/Development/BookCollectionView.swift b/Development/Development/BookCollectionView.swift new file mode 100644 index 0000000..1086502 --- /dev/null +++ b/Development/Development/BookCollectionView.swift @@ -0,0 +1,329 @@ +import CollectionView +import SwiftUI + +struct BookCollectionViewSingleSection: View, PreviewProvider { + + var body: some View { + ContentView() + } + + static var previews: some View { + Self() + .previewDisplayName(nil) + } + + private struct ContentView: View { + + @State var selected: Item? + + var body: some View { + CollectionView( + dataSource: .collection( + data: Item.mock(), + selection: .single( + selected: selected?.id, + onChange: { e in + selected = e + }), + cell: { index, item in + Cell(index: index, item: item) + } + ), + layout: .list { + RoundedRectangle(cornerRadius: 8) + .fill(.secondary) + .frame(height: 8) + .padding(.horizontal, 20) + } + ) + } + } + +} + +struct BookCollectionViewCombined: View, PreviewProvider { + + var body: some View { + ContentView() + } + + static var previews: some View { + Self() + .previewDisplayName(nil) + } + + private struct ContentView: View { + + @State var selected: Item? + @State var selected2: Item? + + var body: some View { + CollectionView( + dataSource: CollectionViewDataSources.Unified { + + Text("Static content") + .overlay(content: { + + }) + + Text("📱❄️") + + CollectionViewDataSources.UsingCollection( + data: Item.mock(10), + selection: .single( + selected: selected?.id, + onChange: { e in + selected = e + }), + cell: { index, item in + Cell(index: index, item: item) + } + ) + + Text("📱❄️") + + CollectionViewDataSources.UsingCollection( + data: Item.mock(10), + selection: .single( + selected: selected2?.id, + onChange: { e in + selected2 = e + }), + cell: { index, item in + Cell(index: index, item: item) + } + ) + + Text("📱❄️") + + ForEach(Item.mock(10)) { item in + Cell(index: item.id, item: item) + } + + }, + layout: .list { + EmptyView() + } + ) + } + } + +} + +#Preview { + CollectionViewDataSources.UsingCollection( + data: Item.mock(10), + selection: .disabled(), + cell: { index, item in + Cell(index: index, item: item) + } + ) +} +//#Preview { +// BookPreview() +//} + +#Preview("Custom List / Single selection") { + + struct Book: View { + + @State var selected: Item? + + var body: some View { + CollectionView( + dataSource: CollectionViewDataSources.UsingCollection( + data: Item.mock(), + selection: .single( + selected: selected?.id, + onChange: { e in + selected = e + }), + cell: { index, item in + Cell(index: index, item: item) + } + ), + layout: .list { + RoundedRectangle(cornerRadius: 8) + .fill(.secondary) + .frame(height: 8) + .padding(.horizontal, 20) + } + ) + } + } + + return Book() +} + +#Preview("Custom List / Multiple selection") { + + struct Book: View { + + @State var selected: Set = .init() + + var body: some View { + + CollectionView( + dataSource: CollectionViewDataSources.UsingCollection( + data: Item.mock(), + selection: .multiple( + selected: selected, + canMoreSelect: selected.count < 3, + onChange: { e, action in + switch action { + case .selected: + selected.insert(e.id) + case .deselected: + selected.remove(e.id) + } + }), + cell: { index, item in + Cell(index: index, item: item) + } + ), + layout: .list { + RoundedRectangle(cornerRadius: 8) + .fill(.secondary) + .frame(height: 8) + .padding(.horizontal, 20) + } + ) + } + } + + return Book() +} + +#Preview("SwiftUI List") { + + CollectionView( + dataSource: CollectionViewDataSources.UsingCollection( + data: Item.mock(), + selection: .disabled(), + cell: { index, item in + HStack { + Text(index.description) + Text(item.title) + } + } + ), + layout: CollectionViewLayouts.PlatformList() + ) +} + +#Preview { + + struct BookList: View { + + struct Ocean: Identifiable, Hashable { + let name: String + let id = UUID() + } + + private var oceans = [ + Ocean(name: "Pacific"), + Ocean(name: "Atlantic"), + Ocean(name: "Indian"), + Ocean(name: "Southern"), + Ocean(name: "Arctic"), + ] + + @State private var multiSelection = Set() + + var body: some View { + NavigationView { + List(oceans, selection: $multiSelection) { + Text($0.name) + } + .navigationTitle("Oceans") + // .toolbar { EditButton() } + } + Text("\(multiSelection.count) selections") + } + + } + + return BookList() +} + +struct Item: Identifiable { + var id: Int + var title: String + + static func mock(_ count: Int = 1000) -> [Item] { + return (0..: CollectionViewSelection { + + private let selected: Item.ID? + private let onChange: (_ selected: Item?) -> Void + private let canSelect: (_ item: Item) -> Bool + + public init( + selected: Item.ID?, + canSelect: @escaping (_ item: Item) -> Bool, + onChange: @escaping (_ selected: Item?) -> Void + ) { + self.selected = selected + self.onChange = onChange + self.canSelect = canSelect + } + + public func isSelected(for id: Item.ID) -> Bool { + self.selected == id + } + + public func isEnabled(for id: Item.ID) -> Bool { + return true + } + + public func update(isSelected: Bool, for item: Item) { + if isSelected { + if canSelect(item) { + onChange(item) + } + } else { + onChange(nil) + } + } + +} diff --git a/Development/Development/ContentView.swift b/Development/Development/ContentView.swift index 00677ec..8736714 100644 --- a/Development/Development/ContentView.swift +++ b/Development/Development/ContentView.swift @@ -25,9 +25,13 @@ struct ContentView: View { BookUIKitBasedFlow() } - NavigationLink("CollectionView Lab") { + NavigationLink("UICollectionView Lab") { BookPlainCollectionView() } + + NavigationLink("CollectionView") { + BookCollectionViewSingleSection() + } } } } diff --git a/Sources/CollectionView/CollectionView+demo.swift b/Sources/CollectionView/CollectionView+demo.swift deleted file mode 100644 index b10b1bb..0000000 --- a/Sources/CollectionView/CollectionView+demo.swift +++ /dev/null @@ -1,121 +0,0 @@ -import SwiftUI - -#if DEBUG - -struct Item: Identifiable { - var id: Int - var title: String - - static func mock() -> [Item] { - return (0..<1000).map { index in - Item(id: index, title: "Item \(index)") - } - } -} - -struct Cell: View { - - @Environment(\.isEnabled) var isEnabled - @Environment(\.collectionView_isSelected) var isSelected - @Environment(\.collectionView_updateSelection) var updateSelection - - let index: Int - let item: Item - - var body: some View { - HStack { - Circle() - .fill(.red) - .frame(width: 20, height: 20) - .opacity(isSelected ? 1 : 0.2) - Text(index.description) - Text(item.title) - Text("isEnabled: \(isEnabled)") - } - ._onButtonGesture(pressing: { _ in }, perform: { - updateSelection(!isSelected) - }) - } -} - -private struct ConfirmingSingle: CollectionViewSelection { - - private let selected: Item.ID? - private let onChange: (_ selected: Item?) -> Void - private let canSelect: (_ item: Item) -> Bool - - public init( - selected: Item.ID?, - canSelect: @escaping (_ item: Item) -> Bool, - onChange: @escaping (_ selected: Item?) -> Void - ) { - self.selected = selected - self.onChange = onChange - self.canSelect = canSelect - } - - public func isSelected(for id: Item.ID) -> Bool { - self.selected == id - } - - public func isEnabled(for id: Item.ID) -> Bool { - return true - } - - public func update(isSelected: Bool, for item: Item) { - if isSelected { - if canSelect(item) { - onChange(item) - } - } else { - onChange(nil) - } - } - -} - -#Preview("Custom List / Single selection") { - - struct Book: View { - - @State var selected: Item? - @State var isAlertPresented = false - - var body: some View { - CollectionView( - items: Item.mock(), - layout: .list { - RoundedRectangle(cornerRadius: 8) - .fill(.secondary) - .frame(height: 8) - .padding(.horizontal, 20) - }, - cell: { index, item in - Cell(index: index, item: item) - } - ) - .selection( - ConfirmingSingle( - selected: selected?.id, - canSelect: { item in - if item.title.contains("Item 1") { - self.isAlertPresented = true - return false - } else { - return true - } - }, - onChange: { selected in - self.selected = selected - }) - ) - .alert("Can not select", isPresented: $isAlertPresented, actions: { - - }) - } - } - - return Book() -} - -#endif diff --git a/Sources/CollectionView/CollectionView.swift b/Sources/CollectionView/CollectionView.swift index 5323ff7..a9af0ab 100644 --- a/Sources/CollectionView/CollectionView.swift +++ b/Sources/CollectionView/CollectionView.swift @@ -1,71 +1,33 @@ import IndexedCollection import SwiftUI + /// Still searching better name /// - built on top of SwiftUI only @available(iOS 16, *) public struct CollectionView< - Data: RandomAccessCollection, - Cell: View, - Layout: CollectionViewLayoutType, - Selection: CollectionViewSelection ->: View where Data.Element: Identifiable { + DataSource: CollectionViewDataSource, + Layout: CollectionViewLayoutType +>: View { - private let cell: (Data.Index, Data.Element) -> Cell + private let dataSource: DataSource private let layout: Layout - private let items: Data - - private var selection: Selection - - public init( - items: Data, - layout: Layout, - @ViewBuilder cell: @escaping (Data.Index, Data.Element) -> Cell - ) where Selection == CollectionViewSelectionModes.None { - self.cell = cell - self.layout = layout - self.items = items - self.selection = .init() - } - + public init( - items: Data, - layout: Layout, - selection: Selection, - @ViewBuilder cell: @escaping (Data.Index, Data.Element) -> Cell + dataSource: DataSource, + layout: Layout ) { - self.cell = cell + self.dataSource = dataSource self.layout = layout - self.items = items - self.selection = selection } - + public var body: some View { - - // for now, switching verbose way - - ForEach(IndexedCollection(items)) { element in - - let isSelected: Bool = selection.isSelected(for: element.id) - let isDisabled: Bool = !selection.isEnabled(for: element.id) - - cell(element.index, element.value) - .disabled(isDisabled) - .environment(\.collectionView_isSelected, isSelected) - .environment(\.collectionView_updateSelection, { [selection] isSelected in - selection.update(isSelected: isSelected, for: element.value) - }) - } - .modifier(layout) - - } - - public consuming func selection>( - _ selection: NewSelection - ) -> CollectionView { - return .init(items: items, layout: layout, selection: selection, cell: cell) + + self.dataSource + .modifier(layout) + } - + } extension EnvironmentValues { @@ -75,124 +37,3 @@ extension EnvironmentValues { extension EnvironmentValues { @Entry public var collectionView_updateSelection: (Bool) -> Void = { _ in } } - -#if DEBUG - -#Preview("Custom List / Single selection") { - - struct Book: View { - - @State var selected: Item? - - var body: some View { - CollectionView( - items: Item.mock(), - layout: .list { - RoundedRectangle(cornerRadius: 8) - .fill(.secondary) - .frame(height: 8) - .padding(.horizontal, 20) - }, - cell: { index, item in - Cell(index: index, item: item) - } - ) - .selection(.single( - selected: selected?.id, - onChange: { e in - selected = e - })) - } - } - - return Book() -} - -#Preview("Custom List / Multiple selection") { - - struct Book: View { - - @State var selected: Set = .init() - - var body: some View { - CollectionView( - items: Item.mock(), - layout: .list { - RoundedRectangle(cornerRadius: 8) - .fill(.secondary) - .frame(height: 8) - .padding(.horizontal, 20) - }, - cell: { index, item in - Cell(index: index, item: item) - } - ) - .selection(.multiple( - selected: selected, - canMoreSelect: selected.count < 3, - onChange: { e, action in - switch action { - case .selected: - selected.insert(e.id) - case .deselected: - selected.remove(e.id) - } - })) - } - } - - return Book() -} - -#Preview("SwiftUI List") { - - CollectionView( - items: Item.mock(), - layout: CollectionViewLayouts.PlatformList(), - cell: { index, item in - HStack { - Text(index.description) - Text(item.title) - } - } - ) -} - -#Preview { - - struct BookList: View { - - struct Ocean: Identifiable, Hashable { - let name: String - let id = UUID() - } - - - private var oceans = [ - Ocean(name: "Pacific"), - Ocean(name: "Atlantic"), - Ocean(name: "Indian"), - Ocean(name: "Southern"), - Ocean(name: "Arctic") - ] - - - @State private var multiSelection = Set() - - var body: some View { - NavigationView { - List(oceans, selection: $multiSelection) { - Text($0.name) - } - .navigationTitle("Oceans") -// .toolbar { EditButton() } - } - Text("\(multiSelection.count) selections") - } - - } - - return BookList() -} - -#endif diff --git a/Sources/CollectionView/CollectionViewDataSource.swift b/Sources/CollectionView/CollectionViewDataSource.swift new file mode 100644 index 0000000..8757740 --- /dev/null +++ b/Sources/CollectionView/CollectionViewDataSource.swift @@ -0,0 +1,90 @@ +import SwiftUI +import IndexedCollection + +public protocol CollectionViewDataSource: View { + +} + +public enum CollectionViewDataSources { + + public struct Unified: CollectionViewDataSource { + + private let content: Content + + public init( + @ViewBuilder content: () -> Content + ) { + self.content = content() + } + + public var body: some View { + content + } + } + + public struct UsingCollection< + Data: RandomAccessCollection, + Cell: View, + Selection: CollectionViewSelection + >: CollectionViewDataSource, View where Data.Element: Identifiable { + + public let data: Data + private let cell: (Data.Index, Data.Element) -> Cell + public let selection: Selection + + public init( + data: Data, + selection: Selection, + cell: @escaping (Data.Index, Data.Element) -> Cell + ) { + self.data = data + self.cell = cell + self.selection = selection + } + + public var body: some View { + ForEach(IndexedCollection(data)) { element in + + let isSelected: Bool = selection.isSelected(for: element.id) + let isDisabled: Bool = !selection.isEnabled(for: element.id) + + cell(element.index, element.value) + .disabled(isDisabled) + .environment(\.collectionView_isSelected, isSelected) + .environment( + \.collectionView_updateSelection, + { [selection] isSelected in + selection.update(isSelected: isSelected, for: element.value) + }) + } + } + + } + +} + +extension CollectionViewDataSource { + + public static func unified( + @ViewBuilder content: () -> Content + ) -> Self where Self == CollectionViewDataSources.Unified { + .init(content: content) + } + + public static func collection< + Data: RandomAccessCollection, + Cell: View, + Selection: CollectionViewSelection + >( + data: Data, + selection: Selection, + cell: @escaping (Data.Index, Data.Element) -> Cell + ) -> Self + where + Self == CollectionViewDataSources.UsingCollection, + Data.Element: Identifiable + { + .init(data: data, selection: selection, cell: cell) + } + +} diff --git a/Sources/CollectionView/CollectionViewLayout.swift b/Sources/CollectionView/CollectionViewLayout.swift index f8fd54b..198fff5 100644 --- a/Sources/CollectionView/CollectionViewLayout.swift +++ b/Sources/CollectionView/CollectionViewLayout.swift @@ -16,6 +16,9 @@ public enum CollectionViewLayouts { public struct PlatformList: CollectionViewLayoutType { + public init() { + } + public func body(content: Content) -> some View { SwiftUI.List { content @@ -45,30 +48,34 @@ public enum CollectionViewLayouts { case .vertical: ScrollView(.vertical) { - LazyVStack { - VariadicViewReader(readingContent: content) { children in - let last = children.last?.id + + UnaryViewReader(readingContent: content) { children in + let last = children.last?.id + LazyVStack { ForEach(children) { child in child if child.id != last { separator + ._identified(by: "separator-\(child.id)") } - } + } } - } - .padding(contentPadding) + } + .padding(contentPadding) + } case .horizontal: ScrollView(.horizontal) { - LazyHStack { - VariadicViewReader(readingContent: content) { children in - let last = children.last?.id + UnaryViewReader(readingContent: content) { children in + let last = children.last?.id + LazyHStack { ForEach(children) { child in child if child.id != last { separator + ._identified(by: "separator-\(child.id)") } } } diff --git a/Sources/CollectionView/CollectionViewSelection.swift b/Sources/CollectionView/CollectionViewSelection.swift index f8f9917..12d02c4 100644 --- a/Sources/CollectionView/CollectionViewSelection.swift +++ b/Sources/CollectionView/CollectionViewSelection.swift @@ -19,7 +19,7 @@ public protocol CollectionViewSelection { } extension CollectionViewSelection { - + public static func single( selected: Item.ID?, onChange: @escaping (_ selected: Item?) -> Void @@ -41,6 +41,10 @@ extension CollectionViewSelection { onChange: onChange ) } + + public static func disabled() -> Self where Self == CollectionViewSelectionModes.None { + .init() + } } diff --git a/Sources/CollectionView/UnaryViewReader.swift b/Sources/CollectionView/UnaryViewReader.swift new file mode 100644 index 0000000..2f152a7 --- /dev/null +++ b/Sources/CollectionView/UnaryViewReader.swift @@ -0,0 +1,107 @@ +import SwiftUI + +/// https://movingparts.io/variadic-views-in-swiftui +struct UnaryViewReader: View { + + let readingContent: ReadingContent + let content: (_VariadicView_Children) -> Content + + init( + readingContent: ReadingContent, + @ViewBuilder content: @escaping (_VariadicView_Children) -> Content + ) { + self.readingContent = readingContent + self.content = content + } + + // MARK: View + + var body: some View { + _VariadicView.Tree(_UnaryView(content: content)) { + readingContent + } + } + +} + +struct MultiViewReader: View { + + let readingContent: ReadingContent + let content: (_VariadicView_Children) -> Content + + init( + readingContent: ReadingContent, + @ViewBuilder content: @escaping (_VariadicView_Children) -> Content + ) { + self.readingContent = readingContent + self.content = content + } + + // MARK: View + + var body: some View { + _VariadicView.Tree(_MultiView(content: content)) { + readingContent + } + } + +} + +private struct _UnaryView: _VariadicView_UnaryViewRoot { + + let content: (_VariadicView_Children) -> Content + + init(@ViewBuilder content: @escaping (_VariadicView_Children) -> Content) { + self.content = content + } + + @ViewBuilder + func body(children: _VariadicView.Children) -> some View { + content(children) + } +} + +private struct _MultiView: _VariadicView_MultiViewRoot { + + let content: (_VariadicView_Children) -> Content + + init(@ViewBuilder content: @escaping (_VariadicView_Children) -> Content) { + self.content = content + } + + @ViewBuilder + func body(children: _VariadicView.Children) -> some View { + content(children) + } +} + +#Preview { + + VStack { + + UnaryViewReader( + readingContent: Group { + + ForEach(0..<10) { index in + Text(index.description) + } + + Text("1") + Text("1") + Text("1") + }, + content: { children in + ForEach(children) { child in + VStack { + HStack { + Text("🐵") + child + } + Text("ID: \(child.id)") + } + } + }) + + } + +} diff --git a/Sources/CollectionView/VariadicViewReader.swift b/Sources/CollectionView/VariadicViewReader.swift deleted file mode 100644 index 1e1c051..0000000 --- a/Sources/CollectionView/VariadicViewReader.swift +++ /dev/null @@ -1,64 +0,0 @@ -import SwiftUI - -/// https://movingparts.io/variadic-views-in-swiftui -struct VariadicViewReader: View { - - let readingContent: ReadingContent - let content: (_VariadicView_Children) -> Content - - init( - readingContent: ReadingContent, - @ViewBuilder content: @escaping (_VariadicView_Children) -> Content - ) { - self.readingContent = readingContent - self.content = content - } - - // MARK: View - - var body: some View { - _VariadicView.Tree(MultiViewForEach(content: content)) { - readingContent - } - } - -} - -private struct MultiViewForEach: _VariadicView_MultiViewRoot { - - let content: (_VariadicView_Children) -> Content - - init(@ViewBuilder content: @escaping (_VariadicView_Children) -> Content) { - self.content = content - } - - // MARK: _VariadicView_MultiViewRoot - - @ViewBuilder - func body(children: _VariadicView.Children) -> some View { - content(children) - } -} - -#Preview { - - VStack { - - VariadicViewReader( - readingContent: Group { - Text("1") - Text("1") - Text("1") - }, - content: { children in - ForEach(children) { child in - HStack { - Text("🐵") - child - } - } - }) - - } - -}