[SwiftUI] ProposedViewSize

SwiftUI2021

     

TAGS:

⌛️ 2 min.

SwiftUI の レイアウト関連のAPIで使用される ProposedViewSize について 確認します。

環境&対象

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

  • macOS14.3 Beta
  • Xcode 15.2 beta
  • iOS 17.2
  • Swift 5.9

ProposedViewSize

iOS16/macOS13 以降で提供されている View のサイズに関する情報の struct です。

Apple のドキュメントは、こちら

ProposedViewSize は、View のサイズ指定に使用される struct なので width・height データで構成されています。

使われるシーン

ProposedViewSize は、以下の API の引数として渡されてきます。
・Layout の sizeThatFits, placeSubviews
・NSViewRepresentable/UIViewRepresentable の sizeThatFits

Layout では、親 View からのサイズ問い合わせ時に、親 View から与えられる情報の1つです。

NSViewRepresentable/UIViewRepresentable では、自身の View のサイズを 返す sizeThatFits が呼び出される時の1つの引数であり、呼び出し側(親 View)から与えられる情報の1つです。

width,height指定での生成

一番ベタ(?)な使い方は、以下のように width・height を指定することです。

let newSize = ProposedViewSize(width: 240, height: 160)

width/height の型は、Optional<CGFloat> ですので、それぞれについて nil を設定することも、CGFloat.infinity を設定することもできます。

nil が設定されているときには、”指定値がない” ことを意味します。つまり、”自身にとって理想的なスペースを使うような要求” を意味します。

CGFloat.infinity が設定されているときには、”最大限のスペースを使うような要求” を意味します。

値として “0” が設定されているときは、”最小限のスペースを使うような要求” を意味します。

width/height それぞれに指定可能ですので、以下のような指定も可能です。

let unspecifiedWidthMaxHeight = ProposedViewSize(width: nil, height: CGFloat.infinity)
let minimumWidthHeight = ProposedViewSize(width: 0, height: 0)

1つめの要求の意味は、幅方向は指定をしない/高さ方向は最大限に使用して良い ということになります。
2つめの要求の意味は、幅方向/高さ方向どちらも最小限のスペースを使用する ということになります。

MEMO

あくまで 親 View からの要求情報であり、かならずしも 整合されたレイアウトになることは保証されません。

static で定義されているデータ

よく使用されるデータが static として以下のように定義されています。

zero
幅・高さ ともに、0
infinity
幅・高さ ともに infinity
unspecified
幅・高さともに 未指定

以下では、それぞれのケースで具体的に設定されている値を確認してみます。

zero

以下のように、width, height ともに、0 が設定されています。つまり 可能な限り小さいサイズを要求していることになります。

print(ProposedViewSize.zero)
// print-out
ProposedViewSize(width: Optional(0.0), height: Optional(0.0))

infinity

以下のように、width, height ともに、CGFloat.infinity が設定されています。

print(ProposedViewSize.infinity)
// print-out
ProposedViewSize(width: Optional(inf), height: Optional(inf))

print(ProposedViewSize.infinity.width == CGFloat.infinity)
// print-out
true

unspecified

以下のように、width, height ともに、nil が設定されています。
先にも確認しましたが、width/height の型は いずれも Optional<CGFloat> ですので、このような値も持ちえます。


print(ProposedViewSize.unspecified)
// print-out
ProposedViewSize(width: nil, height: nil)

便利なメソッド

非常にシンプルな ProposedViewSize 型ですが、計算するときに便利になるようなメソッドが定義されています。

先に見たように ProposedViewSize の width/height は、数値を持つ場合の他に (無指定を意味する) nil を持つことがあります。

仕様としてはわかりますが、実際に ProposedViewSize の値を利用して サイズ計算をしようとすると、nil の場合をきちんと処理しないといけないため、コードが煩雑になりがちです。

そのようなときに、適当(?)に 実数値を持つ CGSize を返してくれるメソッドが用意されています。

replacingUnspecifiedDimensions(by:)

引数に、CGSize を与えることで、nil が指定されている width / height を置き換えて、CGSize を返してくれます。

以下のように nil が指定されている width/height について、引数として与えた値を使用した CGSize になります。

let unspecified = ProposedViewSize.unspecified
let widthUnspecified = ProposedViewSize(width: nil, height: 100)
let widthInfinity = ProposedViewSize(width: .infinity, height: 100)
let heightUnspecified = ProposedViewSize(width: 100, height: nil)
let heightInfinity = ProposedViewSize(width: 100, height: .infinity)
let specified = ProposedViewSize(width: 100, height: 100)

let refSize = CGSize(width: 200, height: 200)

print(unspecified.replacingUnspecifiedDimensions(by: refSize))
// (200.0, 200.0)  refSize そのまま採用

print(widthUnspecified.replacingUnspecifiedDimensions(by: refSize))
// (200.0, 100.0)  refSize の width のみ採用

print(widthInfinity.replacingUnspecifiedDimensions(by: refSize))
// (inf, 100.0)    refSize は無視される(inf はそのまま)

print(heightUnspecified.replacingUnspecifiedDimensions(by: refSize)) 
// (100.0, 200.0)  refSize の height のみ採用

print(heightInfinity.replacingUnspecifiedDimensions(by: refSize)) 
// (100.0, inf)    refSize は無視される(inf はそのまま)

print(specified.replacingUnspecifiedDimensions(by: refSize))         
// (100.0, 100.0)  refSize は 無視される

なお、引数を省略すると、CGSize(width: 10, height: 10) なる CGSize が与えられたものとして処理されます。

print(unspecified.replacingUnspecifiedDimensions())
// (10.0, 10.0)      

print(widthUnspecified.replacingUnspecifiedDimensions())
// (10.0, 100.0)

print(widthInfinity.replacingUnspecifiedDimensions())    
// (inf, 100.0)

print(heightUnspecified.replacingUnspecifiedDimensions())       
// (100.0, 10.0)

print(heightInfinity.replacingUnspecifiedDimensions())          
// (100.0, inf)

print(specified.replacingUnspecifiedDimensions())               
// (100.0, 100.0)
注意

あくまで、nil を置き換えてくれるだけです。
.infinity は .infinity のままです。

まとめ

ProposedViewSize は、View の Size を指定するために用意された struct

ProposedViewSize
  • width と height の情報を Optional<CGFloat> で持つ struct
  • width/height は、.infinity を持つことがあり、最大限のスペースを使うような要求を意味する
  • width/height は、nil を持つことがあり、理想的なスペースを使うような要求を意味する
  • width/height は、0 を持つことがあり、最小限のスペースを使うような要求を意味する

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

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

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