[SwiftUI] SwiftUI の layout システムを理解する ( .frame の働き)

SwiftUI2021

SwiftUI を使用する時によく使う .frame の動作を説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterery beta 4
  • Xcode 13 beta5
  • iOS 15 beta

.frame の働き

SwiftUI には、View Modifier として .frame が用意されています。

.frame を指定すると、対象の View のサイズを指定できると思いがちですが、 じつは、.frame の動作は異なります。

Apple のドキュメントには以下のように説明されています。Apple のドキュメントは、こちら

「Positions this view within an invisible frame with the specified size.」

意訳:指定されたサイズを持つ見えないフレームに、ビューを配置します

「ビューを指定したサイズにします」というのが期待だったかもしれませんが、そのような説明はされていません。

説明文だけでは実際の動作が想像できないので、実際の動作を確認していきます。

.frame の実際の動作

border 表示を使って、どのように動作が変わるかをみていきます。

Text を表示する

変化を確認するために、まずは、.frame を指定していない状態で表示します。

TextWithBorder
TextWithBorder

//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!")
                .border(Color.red)
        }
    }
}

Text が使用する領域にボーダーが表示されました

Text に .frame 指定する

次に Text に .frame 指定してみます。確認のために、border を表示させます。

frame.300x100
frame.300x100

//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!")
                .frame(width: 300, height: 100)
                .border(Color.red)
        }
    }
}

これだけみると、指定したサイズになっているようにも見えます。

この Text と 300x100 のフレームの関係を確認するために、.frame にオプションを追加してみます。

Text の .frame に alignment 指定を追加する

.frame は、「指定されたサイズを持つ見えないフレームに、ビューを配置します」 という機能でした。

.frame のオプションの1つである alignment は、見えないフレーム中のどこにビューを配置するかを指定することができるオプションです。

注意1:デフォルトは、.center です
注意2:フレームのサイズがビューよりも大きい時にのみ機能するオプションです

フレームの右下に配置するために、.bottomTrailing を指定してみます。


//
//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!")
                .frame(width: 300, height: 100, alignment: .bottomTrailing)
                .border(Color.red)
        }
    }
}

結果として以下のような表示になります。

bottomTrailing
bottomTrailing

フレーム中の 右下に表示されていることがわかります。

同時に Text は あくまで必要なサイズ(94.5x20.5 でした) で表示されていて、300x100 で表示されているわけではないこともわかります。

.frame 指定した時の シーケンス

レイアウト時に 以下のようなシーケンスで動作しています。

sequenceDiagram autonumber WindowGroup->>.frame: おすすめサイズは(414.0 x 814.0)だけど, 必要なサイズは? .frame->>Text: おすすめサイズは、(300.0 x 100.0) だけど、必要なサイズは? Text->>.frame: (94.5 x 20.5) あれば、良いです .frame->>WindowGroup: (300,100) 使います。 WindowGroup-->>WindowGroup: .frame を配置しよう .frame-->>.frame: Text を配置しよう

ポイントは、WindowGroup は、直接 Text とやり取りするのではなく .frame 経由でのやりとりに変わるということです。

コード解説
  1. 前回の例と同じですが、スクリーンサイズを .frame に渡します
  2. .frame が Text におすすめサイズを渡し、必要なサイズを確認します
  3. Text は、常に自分が必要とするサイズを返します
  4. .frame は、Text が何を返しても、自分のサイズを WindowGroup に返します (これは、.frame の機能の1つです)
  5. WindowGroup は、.frame から返された情報を使い、自領域の中心に .frame を配置します
  6. .frame は、Text の返した情報を使い、自領域に Text を配置します (これも .frame の働きの1つです)
    このときに、alignment 指定に沿った配置がされます。デフォルトでは、.center です。

.frame の使い方 Tips: SwiftUI での 右寄せ Text

特定の矩形領域に、右寄せで Text を表示したいことはよくあります。

SwiftUI の Text は、自身が必要とするサイズピッタリを使って配置するので、Text の中(?)で、右寄せにする余地はありません。

そのような場合は、.frame を使うことが解決策になります。

複数テキストを、リスト表示

例えば、¥400, ¥10, ¥30,000 といったテキストを右寄せで縦に並べたい時です。

普通にコードを書くと以下のような表示になります。

TextList
TextList

見やすくするために 各 Text に border 指定しています。


//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            List {
                Text("¥400")
                    .border(Color.red)
                Text("¥10")
                    .border(Color.red)
                Text("¥30,000")
                    .border(Color.red)
            }
        }
    }
}

各 Text がちょうどのサイズで表示されているため、右寄せにする余地がないことがわかります。

複数テキストを、リスト中で右寄せ表示

.frame は、見えない指定サイズのフレームの中にビューを配置する ということを利用して、以下のように指定することで、Text を右寄せで表示できるようになります。

幅を最大限確保し、右寄せで配置する


.frame(maxWidth: .infinity, alignment: .trailing)
TextWithTrailing
TextWithTrailing

//
//  LayoutApp.swift
//
//  Created by : Tomoaki Yagishita on 2021/07/29
//  © 2021  SmallDeskSoftware
//

import SwiftUI

@main
struct LayoutApp: App {
    var body: some Scene {
        WindowGroup {
            List {
                Text("¥400")
                    //.border(Color.red)
                    .frame(maxWidth: .infinity, alignment: .trailing)
                    .border(Color.green)
                Text("¥10")
                    .border(Color.red)
                    .frame(maxWidth: .infinity, alignment: .trailing)
                    //.border(Color.green)
                Text("¥30,000")
                    .border(Color.red)
                    .frame(maxWidth: .infinity, alignment: .trailing)
                    //.border(Color.green)
            }
        }
    }
}

¥400 では、.frame の返す箇所に border を表示し、¥10,¥30,000 では、Text の返す矩形を border 表示しています。

.frame が 最大まで width をとっていることがわかります。

まとめ:SwiftUI レイアウト:.frame の働き

SwiftUI レイアウト:.frame の働き
  • .frame は、対象ビューのサイズを変更しない
  • .frame は、対象ビューを支持されたサイズのフレーム中に配置する
  • .frame は、親ビューに 支持されたサイズを返す

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です