Стена входящего текста
Лучше не думать о модификаторах как о модификации 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)
![screen shot of original tutorial code with added borders](https://i.stack.imgur.com/2SFYr.png)
Снимок экрана показывает, что, действительно, кадры двух _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)
}
![screen shot of modified tutorial code with added borders](https://i.stack.imgur.com/gerJR.png)
Здесь мы видим, что игнорирование безопасной области _ModifiedContent
(с синей рамкой на этот раз) имеет тот же кадр, что и в исходном коде: оно начинается в верхней части безопасной области.Но мы также можем видеть, что теперь рамка установки кадра _ModifiedContent
(с красной границей на этот раз) начинается с верхнего края экрана, а не с верхнего края безопасной области, а с нижнего края рамкибыл также сдвинут в той же степени.