Порядок модификаторов в представлении SwiftUI влияет на внешний вид представления - PullRequest
8 голосов
/ 04 июня 2019

Я следую первому учебнику в серии Apple, объясняющему, как создавать и комбинировать представления в приложении SwiftUI.
На шаге 8 раздела 6 учебника нам нужно вставить следующий код:

MapView()
    .edgesIgnoringSafeArea(.top)
    .frame(height: 300)

, который производит следующий пользовательский интерфейс:

image

Теперь я заметил, что при переключении порядка модификаторов в коде следующим образом:

MapView()
    .frame(height: 300) // height set first
    .edgesIgnoringSafeArea(.top)

image

... есть дополнительное пространство между Hello World и картой.

Вопрос

Почему здесь важен порядок модификаторов и как узнать, когда он важен?

Ответы [ 3 ]

16 голосов
/ 05 июня 2019

Стена входящего текста

Лучше не думать о модификаторах как о модификации 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

Снимок экрана показывает, что, действительно, кадры двух _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

Здесь мы видим, что игнорирование безопасной области _ModifiedContent (с синей рамкой на этот раз) имеет тот же кадр, что и в исходном коде: оно начинается в верхней части безопасной области.Но мы также можем видеть, что теперь рамка установки кадра _ModifiedContent (с красной границей на этот раз) начинается с верхнего края экрана, а не с верхнего края безопасной области, а с нижнего края рамкибыл также сдвинут в той же степени.

8 голосов
/ 06 июня 2019

Да. Оно делает. На сессии SwiftUI Essentials Apple попыталась объяснить это как можно проще.

enter image description here

После изменения заказа -

enter image description here

3 голосов
/ 04 июня 2019

Думайте об этих модификаторах как о функциях, которые преобразуют представление. Из этого урока:

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

Имеет смысл то, что порядок имеет значение.

Каким будет результат следующего?

  1. Возьмите лист бумаги
  2. Нарисуйте границу по краю
  3. Вырезать круг

Versus:

  1. Возьмите лист бумаги
  2. вырезать круг
  3. Нарисуйте границу по краю
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...