web-dev-qa-db-fra.com

L'ordre des modificateurs dans la vue SwiftUI affecte l'apparence de la vue

Je suis le premier tutoriel dans la série d'Apple expliquant comment créer et combiner des vues dans une application SwiftUI.
À l'étape 8 de la section 6 du didacticiel, nous devons insérer le code suivant:

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

qui produit l'interface utilisateur suivante:

Maintenant, j'ai remarqué que lors du changement de l'ordre des modificateurs dans le code de la manière suivante:

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

... il y a un espace supplémentaire entre l'étiquette Hello World et la carte.

Question

Pourquoi l'ordre des modificateurs est-il important ici, et comment savoir quand il est important?

10
LinusGeffarth

Mur de texte entrant

Il vaut mieux ne pas penser aux modificateurs comme modifiant le MapView. Au lieu de cela, pensez à MapView().edgesIgnoringSafeArea(.top) comme renvoyant un SafeAreaIgnoringView dont body est le MapView, et qui présente son corps différemment selon que son propre bord supérieur est au bord supérieur de la zone de sécurité. Vous devriez y penser de cette façon parce que c'est ce qu'elle fait réellement.

Comment pouvez-vous être sûr que je dis la vérité? Déposez ce code dans votre méthode application(_:didFinishLaunchingWithOptions:):

_let mapView = MapView()
let safeAreaIgnoringView = mapView.edgesIgnoringSafeArea(.top)
let framedView = safeAreaIgnoringView.frame(height: 300)
print("framedView = \(framedView)")
_

Maintenant, cliquez sur l'option mapView pour voir son type déduit, qui est simple MapView.

Ensuite, cliquez sur safeAreaIgnoringView pour voir son type déduit. Son type est __ModifiedContent<MapView, _SafeAreaIgnoringLayout>_. __ModifiedContent_ est un détail d'implémentation de SwiftUI et il est conforme à View lorsque son premier paramètre générique (nommé Content) est conforme à View. Dans ce cas, son Content est MapView, donc ce __ModifiedContent_ est aussi un View.

Ensuite, cliquez sur framedView pour voir son type déduit. Son type est __ModifiedContent<_ModifiedContent<MapView, _SafeAreaIgnoringLayout>, _FrameLayout>_.

Ainsi, vous pouvez voir qu'au niveau du type, framedView est une vue dont le contenu a le type de safeAreaIgnoringView, et safeAreaIgnoringView est une vue dont le contenu a le type de mapView.

Mais ce ne sont que des types, et la structure imbriquée des types peut ne pas être représentée au moment de l'exécution dans les données réelles, non? Exécutez l'application (sur un simulateur ou un appareil) et regardez la sortie de l'instruction d'impression:

_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)
                    )
                )
            )
    )
_

J'ai reformaté la sortie car Swift l'imprime sur une seule ligne, ce qui la rend très difficile à comprendre.

Quoi qu'il en soit, nous pouvons voir qu'en fait framedView a apparemment une propriété content dont la valeur est le type de safeAreaIgnoringView, et que cet objet a son propre content propriété dont la valeur est un MapView.

Ainsi, lorsque vous appliquez un "modificateur" à un View, vous ne modifiez pas vraiment la vue. Vous créez un nouveau View dont body/content est l'original View.


Maintenant que nous comprenons ce que font les modificateurs (ils construisent le wrapper Views), nous pouvons faire une estimation raisonnable de la façon dont ces deux modificateurs (edgesIgnoringSafeAreas et frame) affectent la disposition .

À un moment donné, SwiftUI traverse l'arborescence pour calculer la trame de chaque vue. Cela commence par la zone de sécurité de l'écran comme cadre de notre ContentView de niveau supérieur. Il visite ensuite le corps de ContentView, qui est (dans le premier tutoriel) un VStack. Pour un VStack, SwiftUI divise le cadre du VStack parmi les enfants de la pile, qui sont trois __ModifiedContent_ s suivis d'un Spacer. SwiftUI regarde à travers les enfants pour comprendre combien d'espace à allouer à chacun. Le premier __ModifiedChild_ (qui contient finalement le MapView) a un modificateur __FrameLayout_ dont height est de 300 points, c'est donc la quantité de VStack est affectée au premier __ModifiedChild_.

Finalement, SwiftUI détermine quelle partie du cadre de VStack assigner à chacun des enfants. Ensuite, il visite chacun des enfants pour attribuer leurs cadres et disposer les enfants des enfants. Il visite donc ce __ModifiedContent_ avec le modificateur __FrameLayout_, en définissant son cadre sur un rectangle qui rencontre le bord supérieur de la zone de sécurité et a une hauteur de 300 points.

Comme la vue est un __ModifiedContent_ avec un modificateur __FrameLayout_ dont height est 300, SwiftUI vérifie que la hauteur attribuée est acceptable pour le modificateur. C'est le cas, donc SwiftUI n'a plus besoin de changer le cadre.

Ensuite, il visite l'enfant de ce __ModifiedContent_, arrivant au __ModifiedContent_ dont le modificateur est `_SafeAreaIgnoringLayout. Il définit le cadre de la vue ignorant la zone de sécurité sur le même cadre que la vue parent (définition du cadre).

SwiftUI doit ensuite calculer le cadre de l'enfant de la vue ignorant la zone de sécurité (le MapView). Par défaut, l'enfant obtient le même cadre que le parent. Mais comme ce parent est un __ModifiedContent_ dont le modificateur est __SafeAreaIgnoringLayout_, SwiftUI sait qu'il pourrait avoir besoin d'ajuster le cadre de l'enfant. Étant donné que le edges du modificateur est défini sur _.top_, SwiftUI compare le bord supérieur du cadre parent au bord supérieur de la zone de sécurité. Dans ce cas, ils coïncident, donc Swift agrandit le cadre de l'enfant pour couvrir l'étendue de l'écran ci-dessus le haut de la zone de sécurité. Ainsi, le cadre de l'enfant s'étend à l'extérieur du cadre du parent.

SwiftUI visite ensuite le MapView et lui assigne le cadre calculé ci-dessus, qui s'étend au-delà de la zone de sécurité jusqu'au bord de l'écran. Ainsi, la hauteur du MapView est de 300 plus l'étendue au-delà du bord supérieur de la zone de sécurité.

Vérifions cela en dessinant une bordure rouge autour de la vue ignorant la zone de sécurité et une bordure bleue autour de la vue de définition du cadre:

_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

La capture d'écran révèle qu'en effet, les images des deux vues __ModifiedContent_ coïncident et ne s'étendent pas en dehors de la zone de sécurité. (Vous devrez peut-être zoomer sur le contenu pour voir les deux bordures.)


Voilà comment SwiftUI fonctionne avec le code dans le projet de tutoriel. Et maintenant, si nous échangeons les modificateurs sur le MapView comme vous l'avez proposé?

Lorsque SwiftUI visite l'enfant VStack du ContentView, il doit répartir l'étendue verticale de VStack parmi les enfants de la pile, comme dans l'exemple précédent.

Cette fois, le premier __ModifiedContent_ est celui avec le modificateur __SafeAreaIgnoringLayout_. SwiftUI voit qu'il n'a pas de hauteur spécifique, il regarde donc l'enfant de __ModifiedContent_, qui est maintenant le __ModifiedContent_ avec le modificateur __FrameLayout_. Cette vue a une hauteur fixe de 300 points, donc SwiftUI sait maintenant que la zone de sécurité ignorant __ModifiedContent_ doit avoir une hauteur de 300 points. SwiftUI accorde donc les 300 premiers points de l'étendue de VStack au premier enfant de la pile (le __ModifiedContent_).

Plus tard, SwiftUI rend visite au premier enfant pour lui assigner son cadre réel et disposer ses enfants. SwiftUI définit donc le cadre de __ModifiedContent_ de la zone de sécurité sur exactement les 300 premiers points de la zone de sécurité.

La prochaine SwiftUI doit calculer la trame de l'enfant de __ModifiedContent_ de zone sûre, qui est la configuration de trame __ModifiedContent_. Normalement, l'enfant obtient le même cadre que le parent. Mais comme le parent est un __ModifiedContent_ avec un modificateur de __SafeAreaIgnoringLayout_ dont edges est _.top_, SwiftUI compare le bord supérieur du cadre du parent au bord supérieur du zone protégée. Dans cet exemple, ils coïncident, donc SwiftUI étend le cadre de l'enfant au bord supérieur de l'écran. Le cadre est donc de 300 points plus l'étendue au-dessus du sommet de la zone de sécurité.

Lorsque SwiftUI va définir le cadre de l'enfant, il voit que l'enfant est un __ModifiedContent_ avec un modificateur de __FrameLayout_ dont height est 300. Puisque le cadre est supérieur à 300 pointe haut, il n'est pas compatible avec le modificateur, donc SwiftUI est obligé d'ajuster le cadre. Il change la hauteur du cadre à 300, mais il ne se retrouve pas avec le même cadre que le parent . L'extension supplémentaire (en dehors de la zone de sécurité) a été ajoutée en haut du cadre, mais la modification de la hauteur du cadre modifie le bord inférieur du cadre.

L'effet final est donc que le cadre est déplacé , plutôt que développé, de l'étendue au-dessus de la zone de sécurité. Le paramètre de cadre __ModifiedContent_ obtient un cadre qui couvre les 300 premiers points de l'écran, plutôt que les 300 premiers points de la zone de sécurité.

SwiftUI visite ensuite l'enfant de la vue de définition de cadre, qui est le MapView, et lui donne le même cadre.

Nous pouvons vérifier cela en utilisant la même technique de dessin de bordure:

_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

Ici, nous pouvons voir que la zone de sécurité ignorant __ModifiedContent_ (avec la bordure bleue cette fois) a le même cadre que dans le code d'origine: elle commence en haut de la zone de sécurité. Mais nous pouvons également voir que maintenant le cadre du paramètre de cadre __ModifiedContent_ (avec la bordure rouge cette fois) commence au bord supérieur de l'écran, pas au bord supérieur de la zone de sécurité et au bord inférieur de le cadre a également été déplacé dans la même mesure.

25
rob mayoff

Oui. Cela fait. Dans la session SwiftUI Essentials, Apple a essayé d'expliquer cela aussi simplement que possible.

enter image description here

Après avoir changé la commande -

enter image description here

11
SMP

Considérez ces modificateurs comme des fonctions qui transforment la vue. De ce tutoriel:

Pour personnaliser une vue SwiftUI, vous appelez des méthodes appelées modificateurs. Les modificateurs encapsulent une vue pour modifier son affichage ou d'autres propriétés. Chaque modificateur renvoie une nouvelle vue, il est donc courant de chaîner plusieurs modificateurs, empilés verticalement.

Il est logique que cet ordre soit important.

Quel serait le résultat de ce qui suit?

  1. Prenez une feuille de papier
  2. Tracez une bordure autour du bord
  3. Découpez un cercle

Contre:

  1. Prenez une feuille de papier
  2. Découpez un cercle
  3. Tracez une bordure autour du bord
4
Tieme