Можно ли в SwiftUI использовать модификатор только для определенной целевой ОС?

Добрый день! Можно ли в SwiftUI использовать модификатор только для определенной целевой ОС? В следующем коде я хотел бы использовать модификатор .listStyle (SidebarListStyle ()) только для цели MacOS, потому что он не существует для цели iOS. Спасибо за помощь.

import SwiftUI

struct ContentView: View {

  @State var selection: Int?

  var body: 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
        .listStyle(SidebarListStyle())
        .frame(maxWidth: .infinity, maxHeight: .infinity)

    } // End HStack
  } // End some View
} // End ContentView

struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

person Wild8x    schedule 23.04.2020    source источник
comment
Вы пробовали использовать #if os(OSX)? Источник   -  person DoesData    schedule 23.04.2020
comment
Да, я пытаюсь использовать #is os (macOS) вокруг самого модификатора, но появляется сообщение об ошибке: Неожиданное состояние платформы (ожидаемое 'os', 'arch' или 'swift') ... Я постараюсь сделать это вокруг HStack .   -  person Wild8x    schedule 23.04.2020
comment
Wild8x, просто замените .listStyle (SidebarListStyle ()) на .navigationViewStyle (DefaultNavigationViewStyle ()), чтобы добиться того, что вам нужно. См. Также мой другой комментарий.   -  person workingdog    schedule 23.04.2020
comment
Спасибо Workingdog. Я только что попробовал, и все нормально! Наконец-то!   -  person Wild8x    schedule 23.04.2020


Ответы (5)


вам лучше сделать это:

 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
 }
person workingdog    schedule 23.04.2020
comment
Но, делая это, не думаете ли вы, что модификатор будет вокруг HStack, а не вокруг NavigationView, как это было изначально? - person Wild8x; 23.04.2020
comment
он будет распространяться на NavigationView. - person workingdog; 23.04.2020

Вы можете создать View расширение и использовать его следующим образом:

List {
    // ...
}
.ifOS(.macOS) {
    $0.listStyle(SidebarListStyle())
}

Вот реализация:

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: (Self) -> Content
    ) -> some View {
        if operatingSystems.contains(OperatingSystem.current) {
            modifier(self)
        } else {
            self
        }
    }
}

Однако это не сработает, если вы попытаетесь использовать метод, который недоступен для всех платформ, на которые вы нацелены. Единственный способ добиться этого - использовать #if os(…) напрямую.

У меня есть расширение, которое упрощает это:

extension View {
    /**
    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)
                    $0.actionSheet(…)
                    #else
                    $0
                    #endif
                }
        }
    }
    ```
    */
    func modify<T: View>(@ViewBuilder modifier: (Self) -> T) -> T {
        modifier(self)
    }
}
person Sindre Sorhus    schedule 30.05.2020
comment
Большое спасибо, Синдре, за ваш ответ. Скоро протестирую. Наконец, на данный момент я принял решение делать только независимые приложения, чтобы избежать беспорядка, связанного с множеством операторов if, если ОС ... но я уверен, что это поможет другим. - person Wild8x; 18.06.2020
comment
Это работает, когда модификатор доступен в нескольких ОС. Ошибка компилятора в некоторых случаях, когда оператор-модификатор недоступен в некоторых ОС. Пример: StackNavigationViewStyle недоступен в macOS .ifOS (.iOS) {$ 0.navigationViewStyle (StackNavigationViewStyle ())} Ошибка компилятора: 'StackNavigationViewStyle' недоступен в macOS - person user3204765; 04.07.2020
comment
Если у вас есть специальный метод для платформы, используйте группу для обертывания: .platform (.iOS) {view in return Group {#if os (iOS) view.background (Color (viewModel.colorName)) .frame (height: UIScreen.main .bounds.height / 1.5) #else view #endif}} - person YannSteph; 29.11.2020
comment
@escaping можно удалить из вызова ifOS, на самом деле он не экранируется. Вы можете сделать modify вот так, чтобы избежать AnyView: func modify<T: View>(@ViewBuilder modifier: ( Self ) -> T) -> T { modifier(self) }. nil не требуется, просто верните $ 0, если никаких изменений не требуется. - person hnh; 09.03.2021
comment
@hnh Хороший улов с спорным @escaping. У меня тоже есть ваша версия modify в моих проектах, но она менее гибкая, поскольку требует, чтобы вы находились в @ViewBuilder контексте, который имеет множество ограничений, например no guard. Но в большинстве случаев ваш действительно лучше. - person Sindre Sorhus; 09.03.2021

Спасибо DoesData за направление.

Решением было использовать #is os (macOS) вокруг всего кода, а не только вокруг самого модификатора.

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()
  }
}
person Wild8x    schedule 23.04.2020

Swift 5.5

Начиная с версии Swift 5.5, вы можете использовать условия добавления непосредственно в модификатор withing.

  } // End NavigationView
    #if os(macOS)
    .listStyle(SidebarListStyle())
    #else
    .navigationViewStyle(DefaultNavigationViewStyle())
    #endif
person Raja Kishan    schedule 11.06.2021

WorkingDog, я пробую ваш элегантный код с очень простым кодом для изменения цвета текста в зависимости от цели ... но текст остается синим на обеих мишенях и не становится красным на 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()
    }
}
person Wild8x    schedule 23.04.2020
comment
ты прав. Я отредактировал свой ответ. Я должен был использовать: #if targetEnvironment (macCatalyst) - person workingdog; 23.04.2020
comment
Большое спасибо, все работает нормально. Проект представляет собой цель iOS с выбранными iOS, iPad и Mac, использующими macCatalyst. - person Wild8x; 23.04.2020