Appleのシリーズの 最初のチュートリアル に従って、SwiftUIアプリケーションでビューを作成および結合する方法を説明しています。
チュートリアルのセクション6のステップ8では、次のコードを挿入する必要があります。
MapView()
.edgesIgnoringSafeArea(.top)
.frame(height: 300)
次のUIが生成されます。
ここで、コード内の修飾子の順序を次のように切り替えると気づきました。
MapView()
.frame(height: 300) // height set first
.edgesIgnoringSafeArea(.top)
...Hello Worldラベルとマップの間に余分なスペースがあります。
ここで修飾子の順序が重要なのはなぜですか。また、重要な場合はどうすればわかりますか?
テキスト着信の壁
修飾子を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
をoption-クリックして、推論された型(単純な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は出力を1行に出力するため、理解が非常に難しくなるため、出力を再フォーマットしました。
とにかく、実際にはframedView
には明らかにcontent
プロパティがあり、その値はsafeAreaIgnoringView
の型であり、そのオブジェクトには独自のcontent
プロパティがあり、その値はMapView
です。
したがって、View
に「修飾子」を適用しても、ビューを実際に変更しているわけではありません。 newView
を作成しています。このbody
/content
は元のView
です。
修飾子が何をするかを理解したところで(ラッパーView
sを構成します)、これらの2つの修飾子(edgesIgnoringSafeAreas
とframe
)がレイアウトにどのように影響するかについて合理的に推測できます。
ある時点で、SwiftUIはツリーを走査して各ビューのフレームを計算します。トップレベルのContentView
のフレームとして、画面の安全領域から始まります。次に、ContentView
の本体を訪問します。これは(最初のチュートリアルでは)VStack
です。 VStack
の場合、SwiftUIはVStack
のフレームをスタックの子に分割します。これは、3つの_ModifiedContent
の後にSpacer
が続くスタックです。 SwiftUIは、子を調べて、それぞれに割り当てるスペースを計算します。最初の_ModifiedChild
(最終的にMapView
を含む)には_FrameLayout
修飾子があり、そのheight
は300ポイントであるため、VStack
の高さの最初の_ModifiedChild
に割り当てられます。
最終的にSwiftUIは、VStack
のフレームのどの部分を各子に割り当てるかを判断します。次に、各子供を訪問してフレームを割り当て、子供を配置します。そのため、_ModifiedContent
修飾子を使用して_FrameLayout
にアクセスし、そのフレームを、安全領域の上端と一致し、高さが300ポイントの長方形に設定します。
ビューは_ModifiedContent
であり、__FrameLayout
修飾子のheight
が300であるため、SwiftUIは、割り当てられた高さが修飾子に受け入れ可能であることを確認します。そのため、SwiftUIはフレームをさらに変更する必要はありません。
次に、その_ModifiedContent
の子を訪問し、修飾子が `_SafeAreaIgnoringLayoutである_ModifiedContent
に到着します。安全領域無視ビューのフレームを、親(フレーム設定)ビューと同じフレームに設定します。
次に、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)
スクリーンショットは、実際には、2つの_ModifiedContent
ビューのフレームが一致しており、安全領域の外にはみ出していないことを示しています。 (両方の境界線を表示するには、コンテンツを拡大する必要がある場合があります。)
これが、SwiftUIがチュートリアルプロジェクトのコードで機能する方法です。では、提案したようにMapView
の修飾子を入れ替えるとどうなるでしょうか。
SwiftUIがVStack
のContentView
子を訪問するとき、前の例と同様に、スタックの子の間でVStack
の垂直範囲を分割する必要があります。
今回は、最初の_ModifiedContent
が_SafeAreaIgnoringLayout
修飾子を持つものです。 SwiftUIは特定の高さがないことを確認しているため、_ModifiedContent
の子を探します。これは、_ModifiedContent
修飾子を使用した_FrameLayout
です。このビューの高さは300ポイントに固定されているため、SwiftUIは、安全領域を無視する_ModifiedContent
の高さが300ポイントであることを認識しています。したがって、SwiftUIは、VStack
の範囲の上位300ポイントをスタックの最初の子(安全領域を無視する_ModifiedContent
)に付与します。
その後、SwiftUIは最初の子にアクセスして、実際のフレームを割り当て、その子をレイアウトします。したがって、SwiftUIは、セーフエリアを無視する_ModifiedContent
のフレームを、セーフエリアの上位300ポイントに正確に設定します。
次に、SwiftUIは、セーフエリアを無視する_ModifiedContent
の子のフレームを計算する必要があります。これは、フレーム設定_ModifiedContent
です。通常、子は親と同じフレームを取得します。ただし、親は_ModifiedContent
の修飾子を持つ_SafeAreaIgnoringLayout
であり、edges
が.top
であるので、SwiftUIは親のフレームの上端を安全領域の上端と比較します。この例では一致しているため、SwiftUIは子のフレームを画面の上端まで拡張します。したがって、フレームは300ポイントに加え、安全領域の上部の範囲になります。
SwiftUIが子のフレームを設定しようとすると、子が_ModifiedContent
であり、修飾子が_FrameLayout
で、height
が300であることがわかります。フレームの高さが300ポイントを超えているため、 tモディファイアと互換性があるため、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)
}
ここで、セーフエリアを無視する_ModifiedContent
(今回は青い境界線)が元のコードと同じフレームを持っていることがわかります。これは、セーフエリアの上部から始まります。しかし、フレーム設定_ModifiedContent
のフレーム(今回は赤い境界線)が、セーフエリアの上部のエッジではなく、画面の上部のエッジから始まり、下部のエッジがフレームも同じだけシフトアップされています。
これらの修飾子は、ビューを変換する関数と考えてください。そのチュートリアルから:
SwiftUIビューをカスタマイズするには、修飾子と呼ばれるメソッドを呼び出します。修飾子はビューをラップして、その表示または他のプロパティを変更します。各モディファイヤは新しいビューを返すため、複数のモディファイヤを縦に重ねてチェーンするのが一般的です。
順序が重要であることは理にかなっています。
次の結果はどうなりますか?
対: