https://github.com/ipedro/swiftui-backport
https://github.com/ipedro/swiftui-backport
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/ipedro/swiftui-backport
- Owner: ipedro
- License: mit
- Created: 2024-11-17T20:38:16.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-11-28T02:05:55.000Z (over 1 year ago)
- Last Synced: 2024-11-28T02:28:30.073Z (over 1 year ago)
- Language: Swift
- Size: 40 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Backport for SwiftUI
[](https://swift.org/package-manager/)
[](https://developer.apple.com/swift/)
[](https://swift.org/download/)
[](LICENSE)
[](https://github.com/ipedro/swiftui-backport/actions)
[](https://github.com/ipedro/swiftui-backport/stargazers)
[](https://github.com/ipedro/swiftui-backport/issues)
[](https://twitter.com/ipedroalmeida)
A lightweight Swift package that backports SwiftUI APIs to iOS versions earlier than latest (18). This allows developers to use newer APIs while maintaining compatibility with older iOS versions.
## Features
- Backports ForEach and Group initializers: Use the subview-based initializers on iOS versions earlier than 18.
- Programmatic access to subviews: Easily manipulate and transform subviews in your SwiftUI views.
- Lightweight and easy to integrate: Minimal code addition to your project.
## Installation
### Swift Package Manager
#### Xcode Project**
You can add the package to your project using Swift Package Manager. In Xcode:
1. Go to File > Add Packages…
2. Enter the repository URL: https://github.com/ipedro/swiftui-backport.git
3. Choose the package and add it to your project.
#### Package.swift
If you’re developing a Swift package and want to add Subviews Backport as a dependency, modify your `Package.swift` file as follows:
1) Add the package to your dependencies array:
```swift
dependencies: [
.package(url: "https://github.com/ipedro/swiftui-backport.git", from: "1.0.0")
]
```
2) Add SwiftUIBackport to your target’s dependencies:
```swift
targets: [
.target(
name: "YourPackage",
dependencies: [
.product(name: "SwiftUIBackport", package: "swiftui-backport")
]
),
// Other targets...
]
```
## Usage
Import the package in your Swift file:
```swift
import SwiftUIBackport
```
### Backported ForEach Initializer
Use the ForEach(subviews:content:) initializer to iterate over the subviews of a given view.
```swift
ForEach(subviews: YourParentView()) { subview in
// Customize each subview
subview
}
```
### Backported Group Initializer
Use the Group(subviews:transform:) initializer to create a group from the subviews of a given view.
```swift
Group(subviews: YourParentView()) { subviews in
VStack {
ForEach(subviews) { subview in
subview
}
}
}
```
## Examples
### Iterating Over Subviews with ForEach
```swift
struct ContentView: View {
var body: some View {
ForEach(subviews: HStack {
Text("First")
Text("Second")
Text("Third")
}) { subview in
subview
.font(.headline)
}
}
}
```
### Custom Layout with Group
```swift
struct CardsView: View {
var body: some View {
Group(subviews: VStack {
Text("Card 1")
Text("Card 2")
Text("Card 3")
}) { subviews in
HStack {
if subviews.count >= 2 {
SecondaryCard { subviews[1] }
}
if let first = subviews.first {
FeatureCard { first }
}
if subviews.count >= 3 {
SecondaryCard { subviews[2] }
}
}
}
}
}
```
### Customizing Subviews in a List
```swift
import SwiftUI
import SwiftUIBackport
struct CustomListView: View {
var body: some View {
Group(subviews: VStack {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}) { subviews in
List {
ForEach(subviews) { subview in
HStack {
Image(systemName: "circle.fill")
.foregroundColor(.blue)
subview
.font(.headline)
}
}
}
}
}
}
```
**Explanation:**
- Purpose: Display a list where each item is a customized version of the subviews.
- Usage: We use Group to access the subviews of a VStack and then construct a List where each subview is customized with additional views and modifiers.
### Custom Stack that Applies Modifiers to Subviews
```swift
import SwiftUI
import SwiftUIBackport
struct CustomStack: View {
let content: Content
let spacing: CGFloat
let alignment: HorizontalAlignment
init(spacing: CGFloat = 10, alignment: HorizontalAlignment = .center, @ViewBuilder content: () -> Content) {
self.spacing = spacing
self.alignment = alignment
self.content = content()
}
var body: some View {
VStack(alignment: alignment, spacing: spacing) {
Group(subviews: content) { subview in
subview
.padding()
.background(Color.yellow.opacity(0.3))
.cornerRadius(8)
}
}
}
}
// Usage
struct CustomStackView: View {
var body: some View {
CustomStack(spacing: 20, alignment: .leading) {
Text("First Item")
Text("Second Item")
Text("Third Item")
}
.padding()
}
}
```
**Explanation:**
- Purpose: Apply the same set of modifiers to each subview.
- Usage: UnaryViewTree processes each subview individually, allowing us to apply modifiers directly.
### Adaptive Stack that Switches Between V/HStack
```swift
import SwiftUI
import SwiftUIBackport
struct AdaptiveStack: View {
let content: Content
let threshold: Int
init(threshold: Int = 3, @ViewBuilder content: () -> Content) {
self.threshold = threshold
self.content = content()
}
var body: some View {
Group(subviews: content) { subviews in
if subviews.count <= threshold {
HStack {
subviews
}
} else {
VStack {
subviews
}
}
}
}
}
// Usage
struct AdaptiveStackExampleView: View {
var body: some View {
AdaptiveStack {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
Text("Item 5")
}
.padding()
}
}
```
**Explanation:**
- Purpose: Create a custom stack that switches between HStack and VStack based on the number of subviews.
- Usage: The AdaptiveStack view takes a threshold parameter. If the number of subviews is less than or equal to the threshold, it uses an HStack; otherwise, it uses a VStack.
- Implementation:
- MultiViewTree: We use MultiViewTree to access the subviews of the provided content.
- Conditional Layout: In the closure, we check subviews.count and choose between HStack and VStack.
- Using subviews Directly: We pass subviews directly into the chosen stack without iterating over them.
**Example Usage:**
```swift
struct AdaptiveStackDemo: View {
var body: some View {
VStack(spacing: 20) {
Text("Adaptive Stack with 2 Items:")
.font(.headline)
AdaptiveStack {
Text("Item A")
Text("Item B")
}
.border(Color.blue)
Text("Adaptive Stack with 5 Items:")
.font(.headline)
AdaptiveStack {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
Text("Item 5")
}
.border(Color.green)
}
.padding()
}
}
```
**Explanation:**
- The first AdaptiveStack has two items and displays them in an HStack.
- The second AdaptiveStack has five items and displays them in a VStack.
### Conditionally Transforming Subviews
```swift
import SwiftUI
import SwiftUIBackport
struct ConditionalTransformationView: View {
var body: some View {
UnaryViewTree(subviews: VStack {
Text("Title").font(.title)
Divider()
Text("Subtitle").font(.subheadline)
Divider()
Text("Body text goes here.").font(.body)
}) { subview in
if subview.body is Divider {
subview
} else {
subview
.foregroundColor(.purple)
}
}
}
}
```
**Explanation:**
- Purpose: Apply transformations based on the type of subview.
- Usage: In the closure, we check if the subview is a Divider and handle it differently from other subviews.
#### Customize Subviews
```swift
import SwiftUI
import SwiftUIBackport
struct CustomizedSubviewsExample: View {
var body: some View {
Group(subviews: HStack {
Text("Apple")
Text("Banana")
Text("Cherry")
}) { subviews in
VStack(alignment: .leading) {
ForEach(subviews.indices, id: \.self) { index in
HStack {
Text("\(index + 1).")
.bold()
subviews[index]
.foregroundColor(.blue)
}
}
}
.padding()
}
}
}
```
## How It Works
The package provides extensions to ForEach and Group that enable the use of subview-based initializers on older iOS versions. It leverages SwiftUI’s _VariadicView system to access and manipulate subviews programmatically.
### Key Components
- _Subview: A proxy representation of a subview, conforming to View and Identifiable.
- `_Subviews`: A collection of `_Subview` instances, conforming to RandomAccessCollection.
- `MultiViewTree`: A view that creates a tree of subviews from an input view.
- `UnaryViewTree`: A view that creates a tree of subviews from an input view.
## Compatibility
- iOS: 13.0+
- macOS: 10.15+
- tvOS: 13.0+
- watchOS: 6.0+
## Limitations
- Subviews are proxies to their resolved views, so modifiers applied to the original view take effect before modifiers applied to the subview.
- Subviews might represent views after styles have been applied; applying additional styles might not always have the desired effect.
## License
Copyright (c) 2024 Pedro Almeida
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Inspired by SwiftUI’s new subview APIs introduced in iOS 18.
- Thanks to the SwiftUI community for ongoing support and contributions.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request for any bugs, feature requests, or improvements.
## Contact
Created by [Pedro Almeida](https://twitter.com/ipedro). Feel free to reach out for any questions or feedback.
Note: This package is a backport and is not affiliated with or endorsed by Apple Inc.