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.
Pourquoi l'ordre des modificateurs est-il important ici, et comment savoir quand il est important?
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 View
s), 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)
_
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)
}
_
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.
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?
Contre: