Использование предпочтений привязки в 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 обрабатывать некоторые функции, связанные с геометрией, внутри.
Вот коммит, содержащий все, что было до этого момента.
Спасибо за прочтение!