Использование предпочтений привязки в SwiftUI

Вступление

Это третья часть из пяти частей. Если вы пришли сюда откуда-то, кроме Части 2, вы можете начать с этого коммита.

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

  • В Части 1 мы настроили проект настраиваемой панели вкладок, не используя предпочтения просмотра и всего лишь одним простым использованием GeometryReader. Мы будем имитировать функции панели вкладок iOS по умолчанию.
  • В Части 2 мы более подробно рассмотрим, как использовать настройки представления, и сделаем нашу иерархию представлений более похожей на SwiftUI.
  • В части 3 мы рассмотрим замену некоторых из наших предпочтений просмотра предпочтениями привязки.

Небольшое примечание: изначально я собирался сделать эту серию из 5 частей, но после написания этих статей я перешел на Flutter. Однако информация во всех трех частях этих статей по-прежнему актуальна, поэтому, если вы хотите узнать больше о SwiftUI, читайте дальше!

Что такое настройки привязки?

Прежде чем вы замените предпочтения просмотра предпочтениями привязки, давайте поговорим о том, что они из себя представляют.

Предпочтения привязки - это подмножество предпочтений представления, т. е. вы используете их в предпочтениях представления, а не полностью заменяете предпочтения представления этой новой концепцией. Это означает, что большая часть кода останется прежней; ваши PreferenceKey и данные о предпочтениях изменятся незначительно.

Прежде всего, взглянем на документацию. В SwiftUI View есть два метода, которые имеют дело с предпочтениями привязки:

func anchorPreference<A, K>(key: K.Type, value: Anchor<A>.Source, transform: (Anchor<A>) -> K.Value) -> View
func transformAnchorPreference<A, K>(key: K.Type, value: Anchor<A>.Source, transform: (inout K.Value, Anchor<A>) -> Void) -> View

Обратите внимание, что эти два метода точно такие же, как и методы для предпочтений просмотра в целом:

func preference<K>(key: K.Type, value: K.Value) -> View
func transformPreference<K>(K.Type, (inout K.Value) -> Void) -> View

Единственная разница в том, что value: K.Value теперь value: Anchor<A>.Source для первого аналогичного метода, а transform: теперь получает Anchor<A> в дополнение к K.Value (K.Value здесь TabBarPreferenceData).

Что такое якорь ‹A›?

Посмотрим документацию:

Непрозрачное значение, полученное из источника привязки и определенного представления.

struct Anchor<Value>

Вы можете преобразовать привязку в Value в координатном пространстве целевого представления, используя LayoutContext для указания целевого представления.

Здесь мало что может нам помочь, но у нас есть некоторая информация, которую следует искать:

  • Есть значение, полученное из "источника привязки" и "определенного представления". Это означает, что привязка каким-то образом связана с представлением.
  • Мы можем преобразовать привязку в Value` в «координатном пространстве» целевого представления, используя что-то, называемое LayoutContext, чтобы указать это целевое представление. Это означает, что предпочтения привязки каким-то образом работают с координатными пространствами представлений. Это Anchor<A> позволяет нам преобразовать некоторую привязку в значение, которое мы можем использовать в координатном пространстве указанного нами представления.

К счастью, в конце документации мы можем перейти к другой связанной теме, Anchor.Source. Поход туда дает нам больше информации:

Значение геометрии со стиранием типа, которое создает привязанное значение заданного типа.

struct Anchor<Value>.Source

SwiftUI передает привязанные геометрические значения по дереву представления с помощью ключей предпочтений. Затем он преобразует их обратно в локальную систему координат с помощью LayoutContext, например, в функции situate(_:updating:in:) макета.

Теперь это становится немного понятнее. Обзор дает нам важную информацию:

SwiftUI передает привязанные геометрические значения по дереву представления с помощью ключей предпочтений. Затем он преобразует их обратно в местную систему координат.

И еще кое-что; если вы посмотрите в конец документации, вы увидите еще несколько свойств типа, например bottom и bounds.

Мы можем вывести предпочтения привязки, позволяющие нам извлекать границы и информацию о привязке (нижнюю, конечную и т. Д.) О представлении относительно координатного пространства этого представления и без необходимости указывать coordinateSpace(name:) в нашей иерархии представлений.

Мы также знаем, что Value в Anchor<Value> либо CGPoint, либо CGRect из этой информации.

Source - это исходный вид, который используется для получения геометрической информации, которую вы ищете.

При этом мы до сих пор не знаем, что такое LayoutContext и что делает situate(_:updaing:in:) (для этого нет официальной документации). Это все внутреннее для SwiftUI, и у нас нет доступа к этому исходному коду. Но это нормально; это просто детали реализации, и нам не нужно о них знать, чтобы использовать настройки привязки.

Использование настроек привязки

Вам нужно будет внести минимальные изменения, чтобы настройки привязки работали. Вы внесете все изменения в ContentView.swift.

Сначала замените тип tabBarBounds в TabBarPreferenceData следующим:

var tabBarBounds: Anchor<CGRect>? = nil

Но как я узнал, что указывать для параметров типа? Поскольку мы ищем границы панели вкладок, она должна быть CGRect. Но не надо гадать. Если вы посмотрите Anchor<A>.Source документацию, как мы видели выше, вы увидите следующее свойство типа:

static var bounds: Anchor<CGRect>.Source

SwiftUI предоставляет CGRect для bounds якоря.

Затем вам нужно будет заменить метод overlay(_:alignment:) на transformAnchorPreference(key:value:transform:). В этом файле есть только один overlay метод, поэтому вы можете найти его и внести следующие изменения:

Использование параметра value изменилось, и теперь есть параметр transform:

  • value: этот параметр позволяет вам выбрать, к какому значению привязки вы хотите получить доступ для текущего представления. Значение здесь - одно из свойств типа, перечисленных в Anchor<A>.Source документации. В этом случае вы используете .bounds, потому что вам нужны границы представления панели вкладок.
  • transform: этот параметр дает value, который вы выбрали для использования, и существующий объект TabBarPreferenceData, и ожидает, что вы измените предпочтение по мере необходимости.

Но почему вам нужно использовать метод transformAnchorPreference вместо метода anchorPreference? Поскольку дочерние представления (CustomTabBarItem s) уже инициализируют TabBarPreferenceData до того, как этот метод будет достигнут. Если вы используете anchorPreference, вы замените все данные, установленные дочерними элементами, на nil, и при использовании этого предпочтения не будет никакого контента для отображения. Следовательно, почему необходимо использовать transformAnchorPreference.

Однако, если бы вы все еще использовали метод overlay, вы могли бы использовать anchorPreference, потому что наложение было бы инициализировано до дочерних представлений.

Кроме того, вам больше не нужно значение GeometryReader GeometryProxy, поэтому вы полностью заменяете метод overlay. Параметры привязки неявно считывают геометрию представления, которое вы их вызываете, поэтому GeometryReader больше не требуется.

Кроме того, вам больше не нужно определять coordinateSpace(name:) в конце вашего основного представления, поэтому вы также можете удалить его. Координатные пространства управляются неявно для вас с помощью предпочтений привязки.

Наконец, вам нужно изменить первую часть метода createTabBarContentOverlay(_:_:), чтобы учесть предпочтения привязки:

Вам нужно только изменить переменную tabBarBounds, чтобы вместо этого использовать предпочтения привязки. Здесь вам нужно использовать geometry, потому что Anchor значения ничего не значат без геометрической справочной системы.

Обозначение, которое вы видите здесь, geometry[preferences.tabBarBounds!], является обозначением Swift subscript. Вы можете увидеть это в GeometryProxy документации; он возвращает CGRect, потому что подпись метода subscript<T>(Anchor<T>) -> T.

Координатное пространство и индекс GeometryProxy

Вам может быть интересно, можно ли использовать какую-либо геометрию для получения значения привязки границ панели вкладок. Вы можете использовать геометрию любого родительского представления, чтобы получить значение привязки по отношению к геометрии этого представления. Однако стоит отметить, что я не пробовал это с одноуровневыми геометриями, поэтому примите это во внимание при использовании предпочтений привязки.

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

Зеленая и красная оси координат показывают положительные оси x и y корневого представления в ContentView, то есть VStack.

Поскольку вы вызываете geometry[preferences.tabBarBounds!], вы получаете границы панели вкладок относительно геометрии вида VStack. В этом случае координаты левого верхнего угла панели вкладок равны (0, 761,5), а ее ширина и высота - (414, 56,5). Если бы вы предоставили другую геометрию, ее границы изменились бы, и она была бы показана в системе отсчета этой геометрии.

Вы можете думать об этом так: если вы получите значение привязки для геометрии на уровне, который вы использовали для определения координатного пространства, то вы получите тот же ответ, что и в части 2 с geometry.frame(in:) вызовами. Таким образом, то, что мы делаем здесь с предпочтениями привязки, аналогично тому, что мы делали в Части 2 с координатными пространствами и предпочтениями.

Та же функциональность, меньше кода

Вот и все! Теперь запустите приложение, и вы увидите, что ничего не изменилось. Однако теперь мы позволяем SwiftUI обрабатывать некоторые функции, связанные с геометрией, внутри.

Вот коммит, содержащий все, что было до этого момента.

Спасибо за прочтение!