Skip to content

Commit f6edf3a

Browse files
CollectionStack version support of iOS, macOS, tvOS, watchOS
- .iOS(.v13), - .macOS(.v10_15), - .tvOS(.v13), - watchOS(.v6) Initial Commit Update README.md
0 parents  commit f6edf3a

File tree

7 files changed

+253
-0
lines changed

7 files changed

+253
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
DerivedData/
7+
.swiftpm/config/registries.json
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.netrc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>IDEDidComputeMac32BitWarning</key>
6+
<true/>
7+
</dict>
8+
</plist>

Package.swift

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// swift-tools-version: 5.7
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "CollectionStack-SwiftUI",
8+
platforms: [
9+
.iOS(.v13),
10+
.macOS(.v10_15),
11+
.tvOS(.v13),
12+
.watchOS(.v6)
13+
],
14+
products: [
15+
// Products define the executables and libraries a package produces, and make them visible to other packages.
16+
.library(
17+
name: "CollectionStack-SwiftUI",
18+
targets: ["CollectionStack-SwiftUI"]),
19+
],
20+
dependencies: [
21+
// Dependencies declare other packages that this package depends on.
22+
// .package(url: /* package url */, from: "1.0.0"),
23+
],
24+
targets: [
25+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
26+
// Targets can depend on other targets in this package, and on products in packages this package depends on.
27+
.target(
28+
name: "CollectionStack-SwiftUI",
29+
dependencies: []),
30+
.testTarget(
31+
name: "CollectionStack-SwiftUITests",
32+
dependencies: ["CollectionStack-SwiftUI"]),
33+
]
34+
)

README.md

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# CollectionStack-SwiftUI
2+
3+
**CollectionStack is designed to solve limitation of HStack and VStack in SwiftU, it grows horizontally first, then vertically if next element could not fit horizontally.**
4+
5+
## CollectionStack
6+
> is generic SwiftUI View over data and content
7+
8+
## Data
9+
> The collection of underlying identified data that CollectionStack uses to create views dynamically.
10+
11+
## Content
12+
> A function to create content on demand using the underlying data. It's a mapper function which maps Data to some View.
13+
14+
##Public API is consistent same as ForEach of SwiftUI
15+
```swift
16+
/// Creates an instance that uniquely identifies and creates views across
17+
/// updates based on the identity of the underlying data.
18+
///
19+
/// It's important that the `id` of a data element doesn't change unless you
20+
/// replace the data element with a new data element that has a new
21+
/// identity. If the `id` of a data element changes, the content view
22+
/// generated from that data element loses any current state and animations.
23+
///
24+
/// - Parameters:
25+
/// - data: The identified data that the ``CollectionStack`` instance uses to
26+
/// create views dynamically.
27+
/// - content: The view builder that creates views dynamically.
28+
29+
public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content) {
30+
self.data = data
31+
self.content = content
32+
}
33+
```
34+
35+
36+
```swift
37+
struct App: Identifiable {
38+
var name: String
39+
var id: String { return name }
40+
}
41+
42+
let apps: [App] = [
43+
"youtube", "podcast", "twitter",
44+
"facebook", "instagram",
45+
"free form", "github desktop", "source tree"
46+
].map(App.init(name: ))
47+
48+
//you can wrap this CollectionStack into ScrollView as well if you want scrolling
49+
CollectionStack(apps) { app in
50+
Button {
51+
print("Selected tag := \(app.name)")
52+
} label: {
53+
Text(app.name)
54+
.font(.system(.callout, design: .rounded, weight: .regular))
55+
}
56+
.buttonStyle(.bordered)
57+
.padding(.trailing, 8)
58+
.padding(.bottom, 8)
59+
}
60+
```
61+
62+
## Requirements
63+
64+
| Platform | Minimum Swift Version | Installation | Status |
65+
| --- | --- | --- | --- |
66+
| iOS 13.0+ | 5.0 | [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested |
67+
| macOS 10.15+ | 5.0 | [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested |
68+
| tvOS 13.0+ | 5.0 | [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested |
69+
| watchOS 6.0+ | 5.0 | [Swift Package Manager](#swift-package-manager), [Manual](#manually) | Fully Tested |
70+
71+
### Swift Package Manager
72+
73+
The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler.
74+
75+
Once you have your Swift package set up, adding CollectionStack-SwiftUI as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
76+
77+
```
78+
dependencies: [
79+
.package(url: "https://github.com/hitendradeveloper/CollectionStack-SwiftUI.git", .upToNextMajor(from: "1.0.0"))
80+
]
81+
```
82+
83+
## License
84+
85+
CollectionStack-SwiftUI is released under the MIT license.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//
2+
// CollectionStack.swift
3+
// CollectionStack-SwiftUI
4+
//
5+
// Created by Hitendra Solanki on 02/02/2023.
6+
// Twitter: @hitendrahckr - Medium: @hitendrahckr
7+
// Copyright © 2023 by Hitendra Solanki. All rights reserved.
8+
//
9+
10+
import SwiftUI
11+
12+
public struct CollectionStack<Data, ID, Content>: View
13+
where Data : RandomAccessCollection, Data.Element : Identifiable, ID : Hashable, ID == Data.Element.ID, Content : View
14+
{
15+
16+
/// The collection of underlying identified data that CollectionStack uses to create
17+
/// views dynamically.
18+
private var data: Data
19+
20+
/// A function to create content on demand using the underlying data.
21+
private var content: (Data.Element) -> Content
22+
23+
@State
24+
private var totalHeight = CGFloat.zero
25+
26+
public var body: some View { bodyWithoutModifiers() }
27+
28+
29+
/// Creates an instance that uniquely identifies and creates views across
30+
/// updates based on the identity of the underlying data.
31+
///
32+
/// It's important that the `id` of a data element doesn't change unless you
33+
/// replace the data element with a new data element that has a new
34+
/// identity. If the `id` of a data element changes, the content view
35+
/// generated from that data element loses any current state and animations.
36+
///
37+
/// - Parameters:
38+
/// - data: The identified data that the ``CollectionStack`` instance uses to
39+
/// create views dynamically.
40+
/// - content: The view builder that creates views dynamically.
41+
public init(_ data: Data, @ViewBuilder content: @escaping (Data.Element) -> Content) {
42+
self.data = data
43+
self.content = content
44+
}
45+
46+
}
47+
48+
fileprivate extension CollectionStack {
49+
func isLast(element: Data.Element) -> Bool {
50+
self.data.last?.id == element.id
51+
}
52+
}
53+
54+
fileprivate extension CollectionStack {
55+
@ViewBuilder
56+
func bodyWithoutModifiers() -> some View {
57+
VStack {
58+
GeometryReader { geometry in
59+
self.generateContent(in: geometry)
60+
}
61+
}
62+
.frame(height: totalHeight)
63+
}
64+
65+
@ViewBuilder
66+
func generateContent(in proxy: GeometryProxy) -> some View {
67+
var width = CGFloat.zero
68+
var height = CGFloat.zero
69+
ZStack(alignment: .topLeading) {
70+
ForEach(self.data) { elementValue in
71+
content(elementValue)
72+
.alignmentGuide(.leading, computeValue: { dimention in
73+
if (abs(width - dimention.width) > proxy.size.width) {
74+
width = 0
75+
height -= dimention.height
76+
}
77+
let result = width
78+
if self.isLast(element: elementValue) {
79+
width = 0
80+
} else {
81+
width -= dimention.width
82+
}
83+
return result
84+
})
85+
.alignmentGuide(.top, computeValue: { _ in
86+
let result = height
87+
if self.isLast(element: elementValue) {
88+
height = 0
89+
}
90+
return result
91+
})
92+
}
93+
}.background(viewHeightUpdatingClearBackground($totalHeight))
94+
}
95+
96+
@ViewBuilder
97+
func viewHeightUpdatingClearBackground(_ binding: Binding<CGFloat>) -> some View {
98+
GeometryReader { geometry -> Color in
99+
let rect = geometry.frame(in: .local)
100+
DispatchQueue.main.async {
101+
binding.wrappedValue = rect.size.height
102+
}
103+
return .clear
104+
}
105+
}
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import XCTest
2+
@testable import CollectionStack_SwiftUI
3+
4+
final class CollectionStack_SwiftUITests: XCTestCase {
5+
func testExample() throws {
6+
// This is an example of a functional test case.
7+
// Use XCTAssert and related functions to verify your tests produce the correct
8+
// results.
9+
XCTAssertEqual(CollectionStack_SwiftUI().text, "Hello, World!")
10+
}
11+
}
47.2 KB
Loading

0 commit comments

Comments
 (0)