SwiftUI-AnySubviews backports iOS 18's subview accessing API to iOS 13, providing a unified API to consistently interact with subviews across different iOS versions.
On iOS 18, SwiftUI introduces a new API Group(subviews:transform:) to allow developers to access the subviews of a given content view. This API is only available for iOS 18. However, there is an API called VariadicView
exists since the first release of SwiftUI, which has almost the same feature with Group(subviews:transform:)
. SwiftUI-AnySubViews unifies these two API so developers can use a consistent API across different iOS versions.
You can check VariadicView's detail on The Moving Parts Team's blog SwiftUI under the Hood: Variadic Views.
- Xcode 16 (Swift 6.0)
- iOS 13, macOS 10.15, tvOS 13, watchOS 6, visionOS 1 or later
Use Swift Package Manager to add this package.
https://github.com/Lumisilk/SwiftUI-AnySubviews.git
If you use SwiftUI-AnySubviews in another package, add this line to your dependencies.
.product(name: "AnySubviews", package: "SwiftUI-AnySubviews")
You can use BackportGroup
basiclly like using Group(subviews:transform:)
.
Check the official documents for further usage.
import AnySubviews
struct CardsView<Content: View>: View {
var content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack {
BackportGroup(subviews: content) { subviews in
HStack {
if subviews.count >= 2 {
SecondaryCard { subview[1] }
}
if let first = subviews.first {
FeatureCard { first }
}
if subviews.count >= 3 {
SecondaryCard { subviews[2] }
}
}
if subviews.count > 3 {
subviews[3...]
}
}
}
}
}
SwiftUI-AnySubViews unifies the following types, ensuring consistent API usage across different iOS versions:
AnySubViews | iOS 18 API | below iOS 18 |
---|---|---|
AnySubview | Subview | _VariadicView_Children.Element |
AnySubviewsCollection | SubviewsCollection | _VariadicView_Children |
AnySubviewsCollectionSlice | SubviewsCollectionSlice | Slice<_VariadicView_Children> |
AnyContainerValues | ContainerValues | _ViewTraitKey |
ContainerValues is also supported in AnySubviews.
To define your custom container values associated with a view, use macro AnyEntry
within AnyContainerValueKeys
's extension:
// Define
extension AnyContainerValueKeys {
// #AnyEntry<Type>(keyName, defaultValue)
#AnyEntry<Int>("myNumber", 0)
#AnyEntry<String>("myName", "Name")
}
// Set
someView
.anyContainerValue(keyPath: \.myNumber, 1)
.anyContainerValue(keyPath: \.myName, "Lumi")
// Read
BackportGroup(subviews: content) { subviews in
ForEach(subviews) { subview in
let number = subview.containerValues.myNumber.description
let name = subview.containerValues.myName
Text(number + " " + name)
}
}
When your app no longer needs to support iOS 17 and earlier versions, you can simplify your codebase:
- Remove the SwiftUI-AnySubviews package from your project completely.
- Replace all occurrences of
BackportGroup
withGroup
throughout your codebase. - For
ContainerValues
especially:- Replace definition part with official API
extension ContainerValues { @Entry ... }
. - Replace setting part with official API
.containerValue(key: MyKey.self, value: 2)
. - Reaplce reading part with official API
subview.containerValues.myCustomValue
.
- Replace definition part with official API
Note: Only proceed with this step when you're certain that your app's minimum supported iOS version is 18 or later.
- Enrich the error desecription of Macro
AnyEntry
- Add a realistic view example using Anysubviews
Contributions are welcome! Please feel free to submit a Issue or Pull Request.
SwiftUI-AnySubViews is released under the MIT license. See LICENSE for details.