Простое кроссплатформенных использование модификаторов в SwiftUI

Когда на Catalyst пишется приложение для macOS, недоступны некоторые модификаторы View. Например, простой refreshable(action:). Его использование вызывает ошибку не компиляции, а исключение в рантайме:

[General] SwiftUI.SwiftUI_UIRefreshControl is not supported when running Catalyst apps in the Mac idiom. See UIBehavioralStyle for possible alternatives. Consider using a Refresh menu item bound to ⌘-R

Из-за этого можно пропустить в продакшн код, который на платформе не работает на самом деле.

Исправить положение можно, отказавшись от модификатора и его функций, можно написать разные компоненты под разные платформы, но есть способ удобнее и лучше: собственный ViewModifier.

Для этого воспользуемся сниппетом для определения платформы от Peter Steinberger из этого поста:

extension UIDevice {
    var isCatalystMacIdiom: Bool {
        if #available(iOS 14, *) {
            return UIDevice.current.userInterfaceIdiom == .mac
        } else {
            return false
        }
    }
}

И с его помощью реализуем примитивный по своей сути модификатор:

struct SafeRefreshable: ViewModifier {

    let action: @Sendable () async -> Void
    
    func body(content: Content) -> some View {
        if UIDevice.current.isCatalystMacIdiom {
            content
        } else {
            content.refreshable(action: action)
        }
    }
}

В нём просто возвращаем content на macOS, которая не поддерживает интересующий нас модификатор SwiftUI. К сожалению, непосредственное использование кастомного модификатор предполагает написание некрасивого кода:

.modifier(SafeRefreshable(action: action))

Исправим это добавив в проект категорию для View:

extension View {
    func safeRefreshable(action: @escaping @Sendable () async -> Void) -> ModifiedContent<Self, SafeRefreshable> {
        return modifier(SafeRefreshable(action: action))
    }
}

Теперь модификатор в коде выглядит нативно:

.safeRefreshable { await refresh() }

Приятного кодинга!


Опубликовано

в

от

Метки: