14 votes

L'ordre des modificateurs dans une vue SwiftUI influe sur 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.
A l'étape 8 de la section 6 du tutoriel, nous devons insérer le code suivant :

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

ce qui produit l'interface utilisateur suivante :

Or, j'ai remarqué qu'en changeant 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 les Bonjour le monde et la carte.

Question

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

28voto

rob mayoff Points 124153

Arrivée d'un mur de texte

Il est préférable de ne pas considérer que les modificateurs modifient les MapView . Pensez plutôt à MapView().edgesIgnoringSafeArea(.top) comme renvoyant un SafeAreaIgnoringView dont body est le MapView et qui dispose son corps différemment selon que son propre bord supérieur se trouve ou non au bord supérieur de la zone de sécurité. C'est ainsi qu'il faut l'envisager, car c'est ce qu'il fait en réalité.

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

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

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

Ensuite, cliquez sur l'option safeAreaIgnoringView pour voir son type déduit. Son type est _ModifiedContent<MapView, _SafeAreaIgnoringLayout> . _ModifiedContent est un détail d'implémentation de SwiftUI et est conforme à View lorsque son premier paramètre générique (nommé Content ) est conforme à la norme View . Dans ce cas, son Content es MapView , de sorte que cette _ModifiedContent est également un View .

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

Vous pouvez donc voir cela au niveau du type, framedView est une vue dont le contenu a le type safeAreaIgnoringView y safeAreaIgnoringView est une vue dont le contenu a le type mapView .

Mais il ne s'agit que de 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, n'est-ce pas ? Exécutez l'application (sur un simulateur ou un appareil) et regardez la sortie de l'instruction 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)
                    )
                )
            )
    )

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 constater qu'en fait framedView a apparemment un content dont la valeur est le type de safeAreaIgnoringView et cet objet a son propre content 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 des enveloppes View ), nous pouvons raisonnablement deviner comment ces deux modificateurs ( edgesIgnoringSafeAreas y frame ) affectent la mise en page.

À un moment donné, SwiftUI parcourt l'arbre pour calculer le cadre de chaque vue. Elle commence par la zone de sécurité de l'écran, qui est le cadre de notre vue de haut niveau ContentView . Il visite ensuite le ContentView qui est (dans le premier tutoriel) une VStack . Pour un VStack SwiftUI divise le cadre de l'article en deux. VStack parmi les enfants de la pile, qui sont au nombre de trois _ModifiedContent suivi d'un Spacer . SwiftUI passe en revue les enfants pour déterminer l'espace qui leur est alloué. Le premier _ModifiedChild (qui contient en fin de compte le MapView ) a un _FrameLayout dont le modificateur height est de 300 points, ce qui correspond à la part de la VStack est assignée à la première _ModifiedChild .

SwiftUI finit par comprendre quelle partie de l'élément VStack à attribuer à chacun des enfants. Il visite ensuite chacun des enfants pour leur assigner leur cadre et disposer les enfants des enfants. Il visite donc ce _ModifiedContent avec le _FrameLayout en fixant son cadre à un rectangle qui rejoint le bord supérieur de la zone de sécurité et qui a une hauteur de 300 points.

Puisque la vue est un _ModifiedContent avec un _FrameLayout dont le modificateur height est 300, SwiftUI vérifie que la hauteur attribuée est acceptable pour le modificateur. C'est le cas, et SwiftUI n'a donc pas besoin de modifier davantage le cadre.

Il visite ensuite l'enfant de ce _ModifiedContent , arrivant à la _ModifiedContent dont le modificateur est `_SafeAreaIgnoringLayout. Il définit le cadre de la vue d'alignement sur la zone de sécurité comme étant le même que celui de la vue parentale (qui définit le cadre).

Ensuite, SwiftUI doit calculer le cadre de l'enfant de la vue qui s'aligne sur la zone de sécurité (l'élément MapView ). Par défaut, l'enfant reçoit le même cadre que le parent. Mais comme ce parent est un _ModifiedContent dont le modificateur est _SafeAreaIgnoringLayout SwiftUI sait qu'il peut être nécessaire d'ajuster le cadre de l'enfant. Étant donné que la fonction edges est fixé à .top SwiftUI compare le bord supérieur du cadre du parent au bord supérieur de la zone de sécurité. Dans ce cas, ils coïncident, donc Swift élargit le cadre de l'enfant pour couvrir la partie de l'écran située au-dessus de la partie supérieure de la zone de sécurité. Le cadre de l'enfant s'étend donc en dehors du cadre du parent.

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

Vérifions-le en traçant une bordure rouge autour de la vue d'alignement de 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 cadres des deux _ModifiedContent coïncident et ne sortent pas de la zone de sécurité. (Il se peut que vous deviez zoomer sur le contenu pour voir les deux bordures).


C'est ainsi que SwiftUI fonctionne avec le code du projet tutoriel. Maintenant, que se passe-t-il si nous intervertissons les modificateurs sur les objets MapView comme vous l'avez proposé ?

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

Cette fois-ci, le premier _ModifiedContent est celui avec le _SafeAreaIgnoringLayout modificateur. SwiftUI constate qu'il n'y a pas de hauteur spécifique, et se tourne donc vers le modificateur _ModifiedContent qui est aujourd'hui l'enfant de la _ModifiedContent avec le _FrameLayout modificateur. Cette vue a une hauteur fixe de 300 points, de sorte que SwiftUI sait maintenant que le modificateur safe-area-ignoring _ModifiedContent devrait être de 300 points. SwiftUI accorde donc les 300 premiers points de la liste des VStack au premier enfant de la pile (l'aliénation de la zone de sécurité _ModifiedContent ).

Plus tard, SwiftUI visite ce premier enfant pour lui assigner son cadre actuel et disposer ses enfants. SwiftUI définit donc la valeur de la zone de sécurité (safe-area-ignoring) _ModifiedContent à exactement les 300 premiers points de la zone de sécurité.

Ensuite, SwiftUI doit calculer le cadre de l'aliénation de la zone de sécurité. _ModifiedContent qui est l'enfant du cadre _ModifiedContent . Normalement, l'enfant reçoit le même cadre que le parent. Mais comme le parent est un _ModifiedContent avec un modificateur de _SafeAreaIgnoringLayout dont edges es .top SwiftUI compare le bord supérieur du cadre du parent au bord supérieur de la zone de sécurité. Dans cet exemple, ils coïncident, et SwiftUI étend donc le cadre de l'enfant jusqu'au bord supérieur de l'écran. Le cadre est donc à 300 points plus l'étendue au-dessus du haut de la zone de sécurité.

Lorsque SwiftUI cherche à définir le cadre de l'enfant, il constate que l'enfant est un _ModifiedContent avec un modificateur de _FrameLayout dont height est de 300. Comme le cadre est plus haut que 300 points, il n'est pas compatible avec le modificateur, et SwiftUI est donc obligé d'ajuster le cadre. Elle ramène la hauteur du cadre à 300, mais il ne se retrouve pas avec le même cadre que le parent . L'étendue supplémentaire (en dehors de la zone de sécurité) a été ajoutée au sommet 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 de l'étendre, en fonction de l'étendue au-dessus de la zone de sécurité. Le 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 qui définit le cadre, qui est la vue MapView et lui donne le même cadre.

Nous pouvons le vérifier en utilisant la même technique de traçage des frontières :

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 l'ignorant de la zone de sécurité _ModifiedContent (avec la bordure bleue cette fois) a le même cadre que dans le code original : il commence en haut de la zone de sécurité. Mais nous pouvons également voir que maintenant le cadre du frame-setting _ModifiedContent (avec la bordure rouge cette fois) commence au bord supérieur de l'écran, et non au bord supérieur de la zone de sécurité, et le bord inférieur du cadre a également été décalé vers le haut dans la même mesure.

14voto

Sada Points 1298

Oui, c'est le cas. Dans la session SwiftUI Essentials, Apple a essayé d'expliquer cela aussi simplement que possible.

enter image description here

Après avoir modifié l'ordre -

enter image description here

4voto

Tieme Points 13569

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

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

Il est logique que cet ordre ait de l'importance.

Quel serait le résultat de l'opération suivante ?

  1. Prendre une feuille de papier
  2. Tracer une bordure autour du bord
  3. Découper un cercle

Versus :

  1. Prendre une feuille de papier
  2. Découper un cercle
  3. Tracer une bordure autour du bord

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X