SwiftUIのレイアウトのアイデアでは、UIKitのレイアウトが同じよりも少し少ないですが、この記事では、SwiftUIのプレイで最も一般的なレイアウトのいくつかを説明することに焦点を当て、これらのレイアウト関連のルールは非常に基本的ですが、これらのテクニックを理解し、非常に必要です。
この記事では、いくつかのポイントを取り上げます:
- frame
- GeometryReader
- Alignment Guide
- Preference
- Stacks
- Spacer
- layoutPriority
レイアウトの法則
次の3つの法則はSwiftUIのレイアウトの最も基本的な法則です。どのようなレイアウトに直面するときでも、これらの3つの法則を考えるだけで、なぜUIがそのような効果になるのか理解できます:
- 親ビューは子ビューの推奨サイズを提供します。
- サブビューは、それ自身の特性に従ってサイズを返します。
- 親ビューは、子ビューから返されたサイズに基づいて、子ビューのレイアウトを作成します。
単純にを取ります:
struct ContentView: View {
var body: some View {
Text("Hello, world")
.border(Color.green)
}
}
ContentViewはTextの親ビューとして、Textのための推奨サイズを提供し、それはこの場合、フルスクリーンサイズです。SwiftUIでは、コンテナのデフォルトのレイアウトは中央揃えです。
上記の3つの基本的なレイアウト・ルールは、プレゼンテーションの残りの部分で何度も繰り返し使用されます。
frame
UIKitでは、フレームはある種の絶対的なレイアウトで、その位置は親ビューの左上隅からの相対的な絶対座標です。しかしSwiftUIでは、修飾子としてのフレームの概念は完全に異なります。
まずはを見てください:
struct ContentView: View {
var body: some View {
Text("Hello, world")
.background(Color.green)
.frame(width: 200, height: 50)
}
}
理想的なディスプレイはこんな感じ:
しかし、実際の効果はこうです:
上記のコードでは、.backgroundは元のテキストを直接修正せず、代わりにテキストレイヤーの下に新しいビューを作成します。
あなたはレイアウトの3つの法則からこの問題を考慮した場合、それは非常に単純になります、.frameの役割は、このケースでは、背景のためのフレームがサイズを提供し、背景も、その子に依頼する必要がある、つまり、テキスト、テキストは、独自のニーズのサイズを返したので、背景もテキストと同じサイズで緑の背景の効果を引き起こしたテキストの実際のサイズを返しました。Textは自分の必要なサイズを返すので、backgroundもTextの実際のサイズを返し、緑色の背景がテキストと同じサイズであるという効果が生じます。
このレイアウトのプロセスを知ると、望みの効果を得るためには、上のコードを少し修正する必要があることがわかります:
struct ContentView: View {
var body: some View {
Text("Hello, world")
.background(Color.green)
.frame(width: 200, height: 50)
}
}
ただ、この機能を達成するために、フレームと背景の順序を調整し、慎重に考えてください、これはなぜですか?スペースを節約するために、あまりにも多くの説明を行うことはありませんが、それはそのようなテキストなどの各ビューの異なる特性は、独自のサイズに戻り、そのような形状として、親ビューの提案サイズに戻り、実際のレイアウトでは、これらの異なる特性の影響を考慮する必要があることは注目に値します。
SwiftUIはフレームの2つの定義を持っています:
func frame(width: CGFloat? = nil, height: CGFloat? = nil, alignment: Alignment = .center) -> some View
widthとheightはnilにすることができ、nilの場合、直接親ビューのサイズを使用します。まず、以下のコードを見てください:
struct ContentView: View {
var body: some View {
HStack {
Text("Good job.")
.background(Color.orange)
}
.frame(width: 300, height: 200, alignment: .topLeading)
.border(Color.green)
}
}
通常の開発で、フレーム内でアライメントを設定してもうまくいかない場合、HStackやVStackなどの外側のコンテナのサイズが、ちょうど子ビューのサイズと等しいことが主な原因ですが、このような場合もアライメントの効果は同じです。この場合の整列の効果は同じです。
すると、フレームの2つ目の定義は次のようになります:
public func frame(minWidth: CGFloat? = nil, idealWidth: CGFloat? = nil, maxWidth: CGFloat? = nil, minHeight: CGFloat? = nil, idealHeight: CGFloat? = nil, maxHeight: CGFloat? = nil, alignment: Alignment = .center) -> some View
この関数にはさらに多くのパラメーターがありますが、一般的に3つのカテゴリーに分類されます:
- minWidth,idealWidth,maxWidth
- minHeight,idealHeight,maxHeight
- alignment
minとmaxの内容については、以下の関係図を見ていただければと思います:
idealWidthとidealHeightについて簡単に説明しましょう。 文字通り、idealは理想的という意味です。では、ビューにidealWidthを設定するとどうなるのでしょうか?
struct ContentView: View {
var body: some View {
Text("Good job.")
.frame(idealWidth: 200, idealHeight: 100)
.border(Color.green)
}
}
実行した結果、Textは指定された理想的なサイズを使用しないことがわかりました:
.fixedSize(horizontal: true, vertical: true)実際、この理想を実現するためには、この理想を併用しなければなりません:
- horizontal: 固定の水平方向、つまりidealWidthを示します。
- vertical: 固定の垂直方向、つまりidealHeightを示します。
もう一度確認してください:
struct ContentView: View {
var body: some View {
HStack {
Text("horizontal")
.frame(idealWidth: 200, idealHeight: 100)
.fixedSize(horizontal: true, vertical: false)
.border(Color.green)
Text("vertical")
.frame(idealWidth: 200, idealHeight: 100)
.fixedSize(horizontal: false, vertical: true)
.border(Color.green)
Text("horizontal & vertical")
.frame(idealWidth: 200, idealHeight: 100)
.fixedSize(horizontal: true, vertical: true)
.border(Color.green)
}
}
}
実際の開発で役立つこのテクニックは、外部条件によってビューのサイズを変更することなく、ビューのサイズを直接固定することができます。フレームの詳細について知りたければ、SwiftUISwiftUIコレクション。詳細をご覧ください。
GeometryReader
フレームを変更することは、親ビューが提案するサイズを変更することと等価であることはすでに知られており、子ビューはこのサイズに基づいて何かを行うことが非常にスマートになりますが、このサイズは今のところまだ暗黙的であり、いわゆる暗黙的とは、このサイズを明示的に取得できないという意味です。
この推奨サイズを明示的に取得したい場合は、GeometryReaderを使用する必要があります:
struct ContentView: View {
@State private var w: CGFloat = 100
@State private var h: CGFloat = 100
var body: some View {
VStack {
GeometryReader { geo in
Text("w: \(geo.size.width, specifier: "%.1f")
h: \(geo.size.height, specifier: "%.1f")")
}
.frame(width: w, height: h)
.border(Color.green)
Slider(value: self.$w, in: 10...300)
.padding(.horizontal, 30)
}
}
}
親ビューの幅を動的に変更する場合、Text は GeometryReader でサイズを取得できます。
struct ContentView: View {
var body: some View {
HStack() {
Spacer()
MyProgress()
.frame(width: 100, height: 100)
Spacer()
MyProgress()
.frame(width: 150, height: 150)
Spacer()
MyProgress()
.frame(width: 300, height: 300)
Spacer()
}
}
}
struct MyProgress: View {
var body: some View {
GeometryReader { geo in
Circle()
.stroke(Color.green, lineWidth: min(geo.size.width, geo.size.height) * 0.2)
}
}
}
この例では、Progressの幅を親ビューの幅に基づいて計算する必要がありますが、これはGeometryReaderの単純な応用にすぎません。
GeometryReaderのもう一つの強力な機能は、特定の座標空間に対するビューの境界を取得できるframe(in)です。
struct ContentView: View {
var body: some View {
VStack {
Spacer()
ForEach(0..<5) { _ in
GeometryReader { geo in
Text("coordinateSpace: \(geo.frame(in: .named("MyVStack")).minY) global: \(geo.frame(in: .global).minY)")
}
.frame(height: 20)
.background(Color.green)
}
Spacer()
}
.frame(height: 300)
.border(Color.green)
.coordinateSpace(name: "MyVStack")
}
}
このように、.named("MyVStack")と.globalではminYの値が異なるため、GeometryReaderの関数も実際には非常に強力で、例えば次のような効果を得ることができます:
GeometryReader の詳細については、SwiftUIジオメトリリーダー を参照してください。
Alignment Guide
コードの多くの場所で.alignを使用していると思いますが、SwiftUIでalignmentが使用される場所の総数は次のとおりです:
このイメージはアライメントを使用することができるすべての方法をカバーしており、今は途方に暮れているかもしれませんが、記事の残りの部分を読み、このイメージを見返した後、まさに古典的であることに気づくでしょう。
上記のコンセプトのいくつかを簡単にご紹介します:
- コンテナのアライメント: コンテナのアライメントには、主に2つの目的があります。まず、内部ビューの暗黙的なアライメントを定義します。)ビューを使用する内部ビューを定義します。コンテナは、引数がコンテナの alignment パラメータと同じ場合にのみ、戻り値に基づいてレイアウトを計算します。
- アライメントガイド:値がコンテナアライメントのパラメータと一致しない場合、その値は有効になりません。
- 暗黙のアライメント値:一般的に、暗黙のアライメントに使用される値はデフォルト値であり、システムは通常、アライメントパラメータに一致する値を使用します。
- 明示的アライメント値:明示的アライメントとは、暗黙的アライメントの逆で、プログラム自身が明示的に与える戻り値のことです。
- Frame Alignment:コンテナ内のビューの配置を示し、ビューを全体として扱い、全体を左、中央、または右に配置します。
- テキストの整列:複数行のテキストの整列を制御します。
もし興味があれば、SwiftUI整列ガイドご覧ください。簡単な例を通して.alignmentGuideの使い方を説明しています。
仮に、以下のような効果を達成する必要があるとします:
コードは次のようになります:
struct ContentView: View {
var body: some View {
Image(systemName: "cloud.bolt.fill")
.resizable()
.frame(width: 50, height: 50)
.padding(10)
.foregroundColor(.white)
.background(RoundedRectangle(cornerRadius: 5).foregroundColor(Color.green.opacity(0.8)))
.addVerifiedBadge(true)
}
}
extension View {
func addVerifiedBadge(_ isVerified: Bool) -> some View {
ZStack(alignment: .topTrailing) {
self
if isVerified {
Image(systemName: "circle.fill")
.foregroundColor(.red)
.offset(x: 10, y: -10)
}
}
}
}
addVerifiedBadgeでは、小さな赤い点の位置のオフセットはoffsetを使用して実装されており、同様に.alignmentGuideを使用して同じ効果を得ることができます。
extension View {
func addVerifiedBadge(_ isVerified: Bool) -> some View {
ZStack(alignment: .topTrailing) {
self
if isVerified {
Image(systemName: "circle.fill")
.foregroundColor(.red)
.alignmentGuide(.top) { (d) -> CGFloat in
d[VerticalAlignment.center]
}
.alignmentGuide(.trailing) { (d) -> CGFloat in
d[HorizontalAlignment.center]
}
}
}
}
}
.alignmentGuideを使用する最大の利点の1つは、ビューの寸法に関する情報を取得できることです。例えば、上のコードではパラメータdを使用しています。
alignmentGuideはとても強力な技術で、特にSwiftUI整列ガイドみることをお勧めします。 一言で言えば、alignmentGuideの核となるアイデアの1つはアライメントを設定することです。
Preference
上記で説明したレイアウトのアイデアは、基本的に子ビューに関連するものです。実際の開発シナリオでは、親ビューが子ビューの内部情報を知る必要があることがよくあります。
まず、このタイプの問題がどのように機能するか見てみましょう。
この例では、緑色の円は、一番上の番号をクリックして移動し、実装の原理は非常に簡単ですが、限り、あなたは情報のこれらの番号の境界を知って、あなたは上記の機能を実現することができ、ここで嗜好関連の知識の使用については、コアのアイデアは、次の2点を持っています:
- 親ビューは、PreferenceKeyに基づいてすべての子ビューのPreferenceDataを取得します。
PreferenceKeyとPreferenceDataを設定するには?基本的には以下のコードで解決します:
struct NumberPreferenceValue: Equatable {
let viewIdx: Int
let rect: CGRect
}
struct NumberPreferenceKey: PreferenceKey {
typealias Value = [NumberPreferenceValue]
static var defaultValue: [NumberPreferenceValue] = []
static func reduce(value: inout [NumberPreferenceValue], nextValue: () -> [NumberPreferenceValue]) {
value.append(contentsOf: nextValue())
}
}
親ビューはどのようにしてこのデータにアクセスするのでしょうか?.onPreferenceChange
var body: some View {
ZStack(alignment: .topLeading) {
...
VStack {
...
}
}
.onPreferenceChange(NumberPreferenceKey.self) { preferences in
for pre in preferences {
self.rects[pre.viewIdx] = pre.rect
}
}
.coordinateSpace(name: "ZStackSpace")
正直なところ、Preferenceのテクニックは学ぶのがとても簡単で、anchorPreferenceのようにサブビューのアンカーを直接取得することができます:
Preferenceの核となる考え方は、親ビューは内部の子ビューにバインドされた情報にアクセスできるというものです。
私が書いた記事では、他にも3つの使い方が紹介されています:
サブビューをリアルタイムで聴くことができます:
バイナリツリーの描画
ドロップダウン リフレッシュ
もっと詳しく知りたい方は、以下の記事をご覧ください;
SwiftUIのビューツリーの練習3
Stacks
SwiftUI 2.0の新しいスタックについては、記事の後半で説明します。
VStackは垂直方向にレイアウトされたコンテナであり、他の制約がない場合、そのレイアウト特性は次のように表されます:サブビューのレイアウト要件を満たそうとし、それ自身の最終的なレイアウトサイズはサブビューのサイズに依存します。
var body: some View {
VStack(spacing: 10) {
Text("Hello, World!")
.background(Color.orange)
Text("Hello, World!")
.background(Color.red)
}
.border(Color.green)
}
HStackは水平にレイアウトされたコンテナで、HStackと同じ特徴を持っています:
var body: some View {
HStack(spacing: 10) {
Text("Hello, World!")
.background(Color.orange)
Text("Hello, World!")
.background(Color.red)
}
.border(Color.green)
}
ZStackは階層的にレイアウトされたコンテナで、後から追加されたビューはその前のビューの上位にあり、HStackやVStackと同じ特徴を持っています:
var body: some View {
ZStack {
Color.orange
.frame(width: 100, height: 50)
Text("Hello, World!")
.border(Color.red)
}
.border(Color.green)
}
これらの3つのコンテナは、開発で最も頻繁に使用されますが、コアのアイデアは非常にシンプルですが、レイアウトのための独自のルールに従って、ビューのコンテナであり、特定の間隔と整列を設定することができます。
Spacer
スペーサーは、より多くのスタックと組み合わせて使用する必要があります。 スペーサーの性質は、特定の方向にできるだけ多くのスペースを取ることです。 ここには方向の概念があり、例えばVスタックでスペーサーを使用すると垂直方向に多くのスペースを取ることになり、逆にHスタックでは水平方向に多くのスペースを取ることになります。
var body: some View {
VStack {
Color.orange
.frame(width: 100, height: 50)
Text("Hello, World!")
.border(Color.red)
Spacer()
}
.border(Color.green)
}
ご覧のように、この時点でVStackの高さはスクリーンのセーフエリア全体の高さになり、Spacerは縦方向に残りのスペースをすべて占めますが、横方向の幅は変わりません。
layoutPriority
コンテナ内にレイアウトされた個々のビューと、その優先順位について疑問に思ったことはありませんか? HStackを例にとると、同じ優先順位を持つ2つのビューが内部にある場合、VStackのスペースを等しく共有することになります:
var body: some View {
HStack {
Color.orange
Text("窓の前の月の光は、地面の霜の疑いがある")
.border(Color.red)
}
.frame(width: 200, height: 100)
.border(Color.green)
}
見ての通り、Textの優先順位は高くなりません。layoutPriorityを使って、例えばTextの優先順位を少し高くするなど、ビューの優先順位を変更することができます:
var body: some View {
HStack {
Color.orange
Text("窓の前の月の光は、地面の霜の疑いがある")
.border(Color.red)
.layoutPriority(1)
}
.frame(width: 200, height: 100)
.border(Color.green)
}
200の幅が唯一のテキストを収容することができるように、したがって、色の左側を表示することはできません、テキストの優先順位を1に設定することができ、非常に大きな値を設定する必要はありませんが、デフォルトでは、優先順位0のビュー。
まとめ
この記事はSwiftUIレイアウトの入門的な内容で、私が書く記事のいくつかは入門的なものではありません。
SwiftUIコレクション:FuckingSwiftUI




