tera
1
How do I specify a conditional availability of a type or an extension, etc, so that either my version or the OS version is getting used depending upon OS version? Example pseudo code (doesn't compile):
@available (iOS, unavailable: 15)
enum HorizontalEdge {
case leading, trailing
}
@available (iOS, unavailable: 15)
extension View {
func swipeActions<T>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, @ViewBuilder content: () -> T) -> some View where T : View {
self // do nothing until iOS 15
}
}
I can think of a more cumbersome solution that uses a different name for "swipeActions", say "swipeActions2" and either calls through OS "swipeActions" on iOS15+ or does nothing on elder systems. Although if there is a way to solve this keeping the name as is, I'd like to do that to keep the main app code as undisturbed as possible (that's why i don't want pollute the main app code base with #if's availability blocks scattered here and there).
1 Like
jflan
(John Flanagan)
2
I don't think there's a way to do what you want with conditional availability. This is the best approach I've seen so far: Simplifying Backwards Compatibility in Swift | Dave DeLong
Here's a playground for "backporting" .swipActions based on the approach described in that article:
import PlaygroundSupport
import SwiftUI
struct Backport<Content> {
let content: Content
init(_ content: Content) {
self.content = content
}
}
extension View {
var backport: Backport<Self> { Backport(self) }
}
extension Backport {
enum HorizontalEdge {
case leading
case trailing
@available(iOS 15, *)
var shadowed: SwiftUI.HorizontalEdge {
switch self {
case .leading: return .leading
case .trailing: return .trailing
}
}
}
}
extension Backport where Content: View {
@ViewBuilder
func swipeActions<T: View>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, content: () -> T) -> some View {
if #available(iOS 15, *) {
self.content.swipeActions(edge: edge.shadowed, allowsFullSwipe: allowsFullSwipe, content: content)
} else {
// Do something else if you want to...
self.content
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
List {
Text("Swipe Me")
.backport.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive, action: {}) {
Image(systemName: "trash.fill")
}
}
}
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
2 Likes
tera
3
Nice. Something is not entirely right here though as I can't specify two buttons inside one "swipeActions" block (although I can workaround it by placing them into two separate swipeActions blocks). figuring out..
// ..... as above
.backport.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive, action: {}) {
Image(systemName: "trash.fill")
}
Button(action: {}) {
Image(systemName: "photo")
}
}
Error: Type '()' cannot conform to 'View'
jflan
(John Flanagan)
4
I forgot to mark the content closure as a ViewBuilder. It should be:
extension Backport where Content: View {
@ViewBuilder
func swipeActions<T: View>(edge: HorizontalEdge = .trailing, allowsFullSwipe: Bool = true, @ViewBuilder content: () -> T) -> some View {
if #available(iOS 15, *) {
self.content.swipeActions(edge: edge.shadowed, allowsFullSwipe: allowsFullSwipe, content: content)
} else {
// Do something else if you want to...
self.content
}
}
}
1 Like
tera
5
Great, thank you.
I managed to sugarize it a bit further:
view
.available(.ios15)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
....
}
This makes it similar to #available (iOS 15, *) checks. And then when time comes - just nuke all them strings - one big red commit.
Still, it would be even better if it was possible without changing the main app sources.