Другое

Исправление ошибки непрозрачного типа ToolbarContent в SwiftUI при архивации

Решение ошибок компиляции SwiftUI с addMatchedTransitionSource при архивации приложения. Узнайте о проблемах с непрозрачными типами, условной доступности и проверенных решениях для расширений ToolbarContent.

Я сталкиваюсь с ошибкой компиляции при архивации SwiftUI-приложения, которое использует кастомное расширение ToolbarContent. Ошибка возникает конкретно с методом addMatchedTransitionSource, но не с removeSharedBackground. Сообщение об ошибке следующее: “opaque type descriptor for <<opaque return type of static SwiftUI.ToolbarContentBuilder.buildLimitedAvailability(SwiftUI.ToolbarContent) -> some>>”, referenced from: lazy protocol witness table accessor for type SwiftUI._ConditionalContent<<<opaque return type of static SwiftUI.ToolbarContentBuilder.buildLimitedAvailability(SwiftUI.ToolbarContent) -> some>>.0, <<opaque return type of static SwiftUI.ToolbarContentBuilder.buildBlock(A) -> some>>.0> and conformance < where A: SwiftUI.ToolbarContent, B: SwiftUI.ToolbarContent> SwiftUI._ConditionalContent<A, B> : SwiftUI.ToolbarContent in SwiftUI in ContentView.o

Вот мое расширение ToolbarContent:

swift
public extension ToolbarContent {

    @available(iOS 17, macOS 13, *)
    @ToolbarContentBuilder
    func removeSharedBackground() -> some ToolbarContent {
        Group {
            if #available(iOS 26, macOS 26, *) {
                self.sharedBackgroundVisibility(.hidden)
            } else {
                self
            }
        }
    }

    @available(iOS 16, macOS 13, *)
    func addMatchedTransitionSource(id: String, in namespace: Namespace.ID?) -> some ToolbarContent {
        Group {
            if #available(iOS 26, *), let namespace {
                self.matchedTransitionSource(id: id, in: namespace)
            } else {
                self
            }
        }
    }
}

А вот как я использую это в своем ContentView:

swift
struct ContentView: View {

    @Namespace private var namespace

    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                Button("Hello", systemImage: "plus") {
                    
                }
            }
            .removeSharedBackground() // Это работает нормально
            .addMatchedTransitionSource(id: "test", in: namespace) // это не работает нормально
        }
    }
}

Странная часть в том, что:

  1. Код успешно компилируется в Xcode (CMD + B)
  2. Ошибка возникает только при попытке архивировать приложение для распространения
  3. Метод removeSharedBackground() работает нормально
  4. Ошибка не возникает, если я помещаю расширение внутрь моего проекта вместо внешнего Swift Package

Есть ли проблема в том, как я определил расширение, или это другой тип ошибки, связанный со Swift Package или процессом архивации?

Ошибка компиляции с методом addMatchedTransitionSource

Ошибка компиляции, с которой вы сталкиваетесь с методом addMatchedTransitionSource, связана с тем, как система непрозрачных типов SwiftUI обрабатывает условную доступность в ToolbarContentBuilder, особенно в процессе архивации. Это, по-видимому, проблема компилятора, которая проявляется конкретно при оптимизации для распространения, а не во время обычных сборок для разработки.


Содержание


Понимание ошибки

Сообщение об ошибке “opaque type descriptor for <<opaque return type of static SwiftUI.ToolbarContentBuilder.buildLimitedAvailability(SwiftUI.ToolbarContent) -> some>>” указывает на проблему разрешения типов в системе сборки SwiftUI. Это происходит потому, что:

  • Компилятору трудно определить точный тип при объединении условной доступности с непрозрачными возвращаемыми типами
  • Ошибка возникает именно при архивации, что указывает на связь с настройками оптимизации или на то, как Swift Package Manager обрабатывает непрозрачные типы на разных этапах компиляции
  • Метод addMatchedTransitionSource имеет другой шаблон доступности по сравнению с removeSharedBackground, что влияет на вывод типов

Как отмечено на форумах для разработчиков Apple, аналогичные проблемы уже сообщались, и иногда требуются обходные пути, такие как добавление исправлений модификаторов.


Анализ основной причины

Основная проблема заключается в том, как Swift обрабатывает непрозрачные типы (some ToolbarContent) в сочетании с условной доступностью. Вот что происходит:

  1. Неоднозначность типов в условных контекстах: Ваш метод addMatchedTransitionSource имеет вложенную условную доступность (@available(iOS 16, macOS 13, *) с внутренним if #available(iOS 26, *)). Это создает сложные отношения между типами, с которыми компилятору трудно справиться при архивации.

  2. Обработка Group и ConditionalContent: Обертка Group в сочетании с условным содержимым создает структуры SwiftUI._ConditionalContent, с которыми система непрозрачных типов испытывает трудности, как упоминается в обсуждении на Stack Overflow.

  3. Компиляция Swift Package по сравнению с проектом: То, что это работает, когда расширение находится внутри вашего проекта, но не работает в Swift Package, указывает на различия в том, как непрозрачные типы обрабатываются на границах модулей и на разных этапах компиляции.

  4. Различия в оптимизации: Обычные сборки (CMD + B) используют разные настройки оптимизации, чем сборки для архивации, что может вызывать тонкие проблемы с разрешением типов.


Решения и обходные пути

Решение 1: Переформируйте метод, чтобы избежать проблем с непрозрачными типами

Измените ваш метод addMatchedTransitionSource, чтобы явно обрабатывать условное содержимое, не полагаясь на непрозрачные возвращаемые типы:

swift
@available(iOS 16, macOS 13, *)
func addMatchedTransitionSource(id: String, in namespace: Namespace.ID?) -> some ToolbarContent {
    if #available(iOS 26, *), let namespace {
        AnyView(
            self.matchedTransitionSource(id: id, in: namespace)
        )
        .toolbar {
            ToolbarItem(placement: .bottomBar) {
                EmptyView()
            }
        }
    } else {
        self
    }
}

Решение 2: Используйте toolbarItemGroup вместо отдельных элементов

Как предложено в обсуждении на Reddit, оберните ваше содержимое панели инструментов в toolbarItemGroup:

swift
.toolbar {
    ToolbarItemGroup(placement: .bottomBar) {
        Button("Hello", systemImage: "plus") {
            // действие
        }
        .removeSharedBackground()
        .addMatchedTransitionSource(id: "test", in: namespace)
    }
}

Решение 3: Упростите условную доступность

Избегайте вложенных проверок доступности, используя более прямой подход:

swift
@available(iOS 16, macOS 13, *)
func addMatchedTransitionSource(id: String, in namespace: Namespace.ID?) -> some ToolbarContent {
    if let namespace, #available(iOS 26, *) {
        return AnyView(self.matchedTransitionSource(id: id, in: namespace))
    }
    return self
}

Решение 4: Используйте явное стирание типов

При работе с условным содержимым рассмотрите возможность использования AnyToolbarContent или явного стирания типов:

swift
struct AnyToolbarContent: ToolbarContent {
    private let _makeContent: (ToolbarContentBuilder) -> Void
    
    init<T: ToolbarContent>(_ content: T) {
        self._makeContent = { builder in
            builder.buildBlock(content)
        }
    }
    
    var body: some ToolbarContent {
        EmptyView()
            .onAppear {
                // Этот подход требует более тщательной реализации
            }
    }
}

Профилактические меры

1. Тестируйте во время разработки с настройками архивации

Регулярно тестируйте ваш код с использованием настроек, аналогичных архивации, во время разработки:

bash
swift build -c release

2. Минимизируйте условную доступность в расширениях

Держите проверки условной доступности как можно проще в расширениях, особенно в тех, которые работают с непрозрачными типами.

3. Используйте лучшие практики ViewBuilder

Следуйте рекомендациям из блога Osas о правильном использовании @ToolbarContentBuilder и избегайте сложной условной логики внутри замыканий сборщика.

4. Рассмотрите структуру модуля

Если проблема сохраняется, рассмотрите возможность перемещения критически важных расширений панели инструментов в основной целевой объект вашего приложения, а не в Swift Package, по крайней мере до тех пор, пока проблема компилятора не будет решена.


Когда сообщать в Apple

Поскольку это, по-видимому, проблема компилятора, которая конкретно влияет на процесс архивации, рассмотрите возможность сообщения об этом в Apple, если:

  1. Обходные пути не полностью решают вашу проблему
  2. Проблема существенно влияет на ваш производственный процесс
  3. Вы можете создать минимальный воспроизводимый случай

При сообщении об ошибке включите:

  • Вашу версию Xcode
  • Точное сообщение об ошибке
  • Упрощенный пример кода, воспроизводящего проблему
  • Информацию о том, возникает ли она в проектах по сравнению с пакетами

Эта ошибка компиляции подчеркивает сложное взаимодействие между системой непрозрачных типов Swift, условной доступностью и инфраструктурой сборки SwiftUI, особенно в разных контекстах компиляции.

Авторы
Проверено модерацией
Модерация