Good day! In SwiftUI, is it possible to use a modifier only for a certain os target? In the following code I would like to use the modifier .listStyle(SidebarListStyle()) on
You can create a View
extension and use it like this:
List {
// ...
}
.ifOS(.macOS) {
$0.listStyle(SidebarListStyle())
}
Here's the implementation:
enum OperatingSystem {
case macOS
case iOS
case tvOS
case watchOS
#if os(macOS)
static let current = macOS
#elseif os(iOS)
static let current = iOS
#elseif os(tvOS)
static let current = tvOS
#elseif os(watchOS)
static let current = watchOS
#else
#error("Unsupported platform")
#endif
}
extension View {
/**
Conditionally apply modifiers depending on the target operating system.
```
struct ContentView: View {
var body: some View {
Text("Unicorn")
.font(.system(size: 10))
.ifOS(.macOS, .tvOS) {
$0.font(.system(size: 20))
}
}
}
```
*/
@ViewBuilder
func ifOS<Content: View>(
_ operatingSystems: OperatingSystem...,
modifier: @escaping (Self) -> Content
) -> some View {
if operatingSystems.contains(OperatingSystem.current) {
modifier(self)
} else {
self
}
}
}
However, this will not work if you try to use a method that is not available for all the platforms you target. The only way to make that work is to use #if os(…)
directly.
I have an extension that makes it easier to do that:
extension View {
/// Returns a type-erased version of `self`.
func eraseToAnyView() -> AnyView {
AnyView(self)
}
}
extension View {
// The closure unfortunately has to return `AnyView` as `some` cannot yet be used in return values in closures.
/**
Modify the view in a closure. This can be useful when you need to conditionally apply a modifier that is unavailable on certain platforms.
For example, imagine this code needing to run on macOS too where `View#actionSheet()` is not available:
```
struct ContentView: View {
var body: some View {
Text("Unicorn")
.modify {
#if os(iOS)
return $0.actionSheet(…).eraseToAnyView()
#endif
return nil
}
}
}
```
*/
@ViewBuilder
func modify(_ modifier: (Self) -> AnyView?) -> some View {
if let view = modifier(self) {
view
} else {
self
}
}
}
Thanks DoesData for giving me the direction.
The solution was to use #is os(macOS) around the entire code and not only around the modifier itself.
import SwiftUI
struct ContentView: View {
@State var selection: Int?
var body: some View {
#if os(macOS)
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.listStyle(SidebarListStyle())
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
#elseif os(iOS)
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
#endif
} // End some View
} // End ContentView
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
WorkingDog, I try your elegante code with a very simple code to change the text color depending on the Target... but the text stays blue on both target and does not go red on MacOS!
import SwiftUI
struct ContentView: View {
var body: some View {
#if os(macOS)
return monText.foregroundColor(Color.red)
#elseif os(iOS)
return monText.foregroundColor(Color.blue)
#endif
}
var monText: some View {
Text("Hello, World!")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
your better off doing this:
import SwiftUI
struct ContentView: View {
@State var selection: Int?
var body: some View {
#if targetEnvironment(macCatalyst)
return theList.listStyle(SidebarListStyle())
#else
return theList.navigationViewStyle(DefaultNavigationViewStyle())
#endif
}
var theList: some View {
HStack() {
NavigationView {
List () {
NavigationLink(destination: FirstView(), tag: 0, selection: self.$selection) {
Text("Click Me To Display The First View")
} // End Navigation Link
NavigationLink(destination: SecondView(), tag: 1, selection: self.$selection) {
Text("Click Me To Display The Second View")
} // End Navigation Link
} // End list
.frame(minWidth: 350, maxWidth: 350)
.onAppear {
self.selection = 0
}
} // End NavigationView
.frame(maxWidth: .infinity, maxHeight: .infinity)
} // End HStack
} // End some View
} // End ContentView
}