Порядок модификаторов в представлении SwiftUI влияет на внешний вид представления
Я следую за первым учебным пособием в серии Apple, объясняющим, как создавать и комбинировать представления в приложении SwiftUI.
На шаге 8 раздела 6 учебника нам нужно вставить следующий код:
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
который производит следующий интерфейс:
Теперь я заметил, что при переключении порядка модификаторов в коде следующим образом:
MapView()
.frame(height: 300) // height set first
.edgesIgnoringSafeArea(.top)
... между надписью Hello World и картой есть дополнительное пространство.
Вопрос
Почему порядок модификаторов здесь важен, и как я узнаю, когда он важен?
3 ответа
Стена текста входящего
Лучше не думать о модификаторах как об изменении MapView
, Вместо этого подумайте о MapView().edgesIgnoringSafeArea(.top)
как возвращение SafeAreaIgnoringView
чья body
это MapView
и который размещает свое тело по-разному в зависимости от того, находится ли его собственный верхний край у верхнего края безопасной области. Вы должны думать об этом таким образом, потому что это то, что он на самом деле делает.
Как ты можешь быть уверен, что я говорю правду? Перетащите этот код в свой application(_:didFinishLaunchingWithOptions:)
метод:
let mapView = MapView()
let safeAreaIgnoringView = mapView.edgesIgnoringSafeArea(.top)
let framedView = safeAreaIgnoringView.frame(height: 300)
print("framedView = \(framedView)")
Теперь опция-нажмите mapView
чтобы увидеть его предполагаемый тип, который является простым MapView
,
Далее, щелчок по опции safeAreaIgnoringView
чтобы увидеть его предполагаемый тип. Его тип _ModifiedContent<MapView, _SafeAreaIgnoringLayout>
, _ModifiedContent
это деталь реализации SwiftUI, и она соответствует View
когда его первый общий параметр (названный Content
) соответствует View
, В этом случае его Content
является MapView
так вот _ModifiedContent
также View
,
Далее, щелчок по опции framedView
чтобы увидеть его предполагаемый тип. Его тип _ModifiedContent<_ModifiedContent<MapView, _SafeAreaIgnoringLayout>, _FrameLayout>
,
Таким образом, вы можете видеть, что на уровне типа, framedView
это вид, содержание которого имеет тип safeAreaIgnoringView
, а также safeAreaIgnoringView
это вид, содержание которого имеет тип mapView
,
Но это только типы, и вложенная структура типов может не быть представлена во время выполнения в реальных данных, верно? Запустите приложение (на симуляторе или устройстве) и посмотрите на вывод оператора print:
framedView =
_ModifiedContent<
_ModifiedContent<
MapView,
_SafeAreaIgnoringLayout
>,
_FrameLayout
>(
content:
SwiftUI._ModifiedContent<
Landmarks.MapView,
SwiftUI._SafeAreaIgnoringLayout
>(
content: Landmarks.MapView(),
modifier: SwiftUI._SafeAreaIgnoringLayout(
edges: SwiftUI.Edge.Set(rawValue: 1)
)
),
modifier:
SwiftUI._FrameLayout(
width: nil,
height: Optional(300.0),
alignment: SwiftUI.Alignment(
horizontal: SwiftUI.HorizontalAlignment(
key: SwiftUI.AlignmentKey(bits: 4484726064)
),
vertical: SwiftUI.VerticalAlignment(
key: SwiftUI.AlignmentKey(bits: 4484726041)
)
)
)
)
Я переформатировал вывод, потому что Swift печатает его в одной строке, что делает его очень трудным для понимания.
Во всяком случае, мы можем видеть, что на самом деле framedView
по-видимому, имеет content
свойство, значением которого является тип safeAreaIgnoringView
и этот объект имеет свой собственный content
свойство, стоимость которого MapView
,
Итак, когда вы применяете "модификатор" к View
Вы действительно не изменяете представление. Вы создаете новый View
чья body
/ content
это оригинал View
,
Теперь, когда мы понимаем, что делают модификаторы (они создают оболочку View
s) мы можем сделать разумное предположение о том, как эти два модификатора (edgesIgnoringSafeAreas
а также frame
) влияет на макет.
В какой-то момент SwiftUI пересекает дерево, чтобы вычислить кадр каждого представления. Это начинается с безопасной области экрана как рамки нашего верхнего уровня ContentView
, Затем он посещает ContentView
тело, которое является (в первом уроке) VStack
, Для VStack
SwiftUI делит кадр VStack
среди детей стека, которых три _ModifiedContent
с последующим Spacer
, SwiftUI просматривает детей, чтобы выяснить, сколько места нужно выделить каждому. Первый _ModifiedChild
(который в конечном итоге содержит MapView
) имеет _FrameLayout
модификатор которого height
300 баллов, так вот сколько VStack
высота присваивается первой _ModifiedChild
,
В конечном итоге SwiftUI выясняет, какая часть VStack
Кадр назначить каждому из детей. Затем он посещает каждого из детей, чтобы определить свои рамки и выложить детей. Так что это посещает _ModifiedContent
с _FrameLayout
модификатор, устанавливающий его рамку в виде прямоугольника, который соответствует верхнему краю безопасной области и имеет высоту 300 точек.
Так как вид _ModifiedContent
с _FrameLayout
модификатор которого height
равен 300, SwiftUI проверяет, что назначенная высота является приемлемой для модификатора. Это так, так что SwiftUI не нужно менять кадр дальше.
Затем он посещает ребенка этого _ModifiedContent
, достигнув _ModifiedContent
чей модификатор `_SafeAreaIgnoringLayout. Он устанавливает фрейм представления игнорирования безопасной области на тот же фрейм, что и родительский вид (установка фрейма).
Затем SwiftUI должен вычислить кадр дочернего элемента представления, игнорирующего безопасную область (MapView
). По умолчанию дочерний элемент получает тот же кадр, что и родительский. Но так как этот родитель _ModifiedContent
чей модификатор _SafeAreaIgnoringLayout
SwiftUI знает, что может потребоваться настроить рамку ребенка. Поскольку модификатор edges
установлен в .top
SwiftUI сравнивает верхний край родительского фрейма с верхним краем безопасной области. В этом случае они совпадают, поэтому Swift расширяет рамку дочернего элемента, чтобы охватить область экрана над верхней частью безопасной области. Таким образом, рамка ребенка выходит за рамки рамки родителя.
Затем SwiftUI посещает MapView
и назначает ему кадр, вычисленный выше, который выходит за пределы безопасной области до края экрана. Таким образом MapView
Высота составляет 300 плюс степень за верхним краем безопасной зоны.
Давайте проверим это, нарисовав красную рамку вокруг представления, игнорирующего безопасную область, и синюю рамку вокруг представления с установкой кадра:
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
Снимок экрана показывает, что, действительно, кадры двух _ModifiedContent
взгляды совпадают и не выходят за пределы безопасной зоны. (Возможно, вам придется увеличить содержимое, чтобы увидеть обе границы.)
Вот как SwiftUI работает с кодом в учебном проекте. А что если мы поменяем модификаторы на MapView
вокруг, как вы предложили?
Когда SwiftUI посещает VStack
дитя ContentView
нужно поделить VStack
вертикальный экстент среди дочерних элементов стека, как в предыдущем примере.
На этот раз первый _ModifiedContent
это тот, с _SafeAreaIgnoringLayout
модификатор. SwiftUI видит, что у него нет определенной высоты, поэтому он смотрит на _ModifiedContent
дитя, которое сейчас _ModifiedContent
с _FrameLayout
модификатор. Эта точка зрения имеет фиксированную высоту 300 точек, поэтому SwiftUI теперь знает, что игнорирование безопасной области _ModifiedContent
должно быть 300 баллов. Так что SwiftUI предоставляет 300 лучших очков VStack
экстент до первого потомка стека (игнорирование безопасной области _ModifiedContent
).
Позже SwiftUI посещает этого первого потомка, чтобы назначить его реальный кадр и выложить его потомков. Так что SwiftUI устанавливает игнорирование безопасной области _ModifiedContent
Кадр с точностью до 300 верхних точек безопасной зоны.
Далее SwiftUI необходимо вычислить кадр игнорирования безопасной области _ModifiedContent
дитя, которое является установкой кадра _ModifiedContent
, Обычно ребенок получает тот же кадр, что и родитель. Но так как родитель является _ModifiedContent
с модификатором _SafeAreaIgnoringLayout
чья edges
является .top
SwiftUI сравнивает верхний край родительского фрейма с верхним краем безопасной области. В этом примере они совпадают, поэтому SwiftUI расширяет рамку дочернего элемента до верхнего края экрана. Таким образом, рамка составляет 300 точек плюс расстояние над вершиной безопасной зоны.
Когда SwiftUI идет, чтобы установить рамку дочернего элемента, он видит, что дочерний элемент _ModifiedContent
с модификатором _FrameLayout
чья height
равно 300. Поскольку высота кадра превышает 300, он не совместим с модификатором, поэтому SwiftUI вынужден корректировать кадр. Он изменяет высоту кадра до 300, но не заканчивается тем же кадром, что и родительский. Дополнительный экстент (за пределами безопасной области) был добавлен в верхнюю часть рамки, но изменение высоты рамки изменяет нижний край рамки.
Таким образом, конечный эффект состоит в том, что рамка перемещается, а не расширяется на величину, превышающую безопасную область. Настройка кадра _ModifiedContent
получает кадр, который покрывает верхние 300 точек экрана, а не верхние 300 точек безопасной области.
Затем SwiftUI посещает дочерний элемент представления настройки фрейма, которое является MapView
и дает ему тот же кадр.
Мы можем проверить это, используя ту же технику рисования границ:
if false {
// Original tutorial modifier order
MapView()
.edgesIgnoringSafeArea(.top)
.border(Color.red, width: 2)
.frame(height: 300)
.border(Color.blue, width: 1)
} else {
// LinusGeffarth's reversed modifier order
MapView()
.frame(height: 300)
.border(Color.red, width: 2)
.edgesIgnoringSafeArea(.top)
.border(Color.blue, width: 1)
}
Здесь мы видим, что игнорирование безопасной зоны _ModifiedContent
(с синей рамкой на этот раз) имеет тот же кадр, что и в исходном коде: он начинается в верхней части безопасной области. Но мы также можем видеть, что теперь кадр настройки кадра _ModifiedContent
(с красной рамкой на этот раз) начинается с верхнего края экрана, а не с верхнего края безопасной области, и нижний край рамки также был смещен вверх в той же степени.
Да. Оно делает. На сессии SwiftUI Essentials Apple попыталась объяснить это как можно проще.
После изменения заказа -
Думайте об этих модификаторах как о функциях, которые преобразуют представление. Из этого урока:
Чтобы настроить представление SwiftUI, вы вызываете методы, называемые модификаторами. Модификаторы переносят представление, чтобы изменить его отображение или другие свойства. Каждый модификатор возвращает новое представление, поэтому обычно объединяют несколько модификаторов, сложенных вертикально.
Это имеет смысл, что порядок имеет значение.
Каков будет результат следующего?
- Возьми лист бумаги
- Нарисуйте границу вокруг края
- Вырезать круг
Против:
- Возьми лист бумаги
- Вырезать круг
- Нарисуйте границу вокруг края