Sponsor Link
環境&対象
- macOS Big Sur 11.3
- Xcode 12.5
- iOS 14.5
View の大きさを調整したい
View の大きさを調整するときに まず浮かぶのは、.frame だと思います。
しかし、.frame は、View に 大きさの提案をするだけで 指定された View が その大きさになることは保証されません。
誰が View の大きさを決定するか
例えば、子 View が、大きさ 200×200 のサイズになろうとしている時に、親 View から、300×300 と指定されても、子 View の大きさは、200×200 のままです。
親 View からの提案は、あくまで提案で 最終的にサイズを決定するのは、子 View です。
ちなみに、親 View が提案した 300×300 の中の どの位置に 200×200 の 子 View を配置するかは、親 View 次第です。
View の大きさ調整の例
Image のケース
SwiftUI の View として用意されている Image には、便利な modifier があります。
.resizable です。この view modifier を指定すると、提案されたサイズにあうように View のサイズを調整してくれます。
調整する時に 縦横の比率については、.scaleToFit, .scaleToFill, .aspectRatio 等の view modifier で調整することが可能です。
つまり、大きさを変更したい View が Image である時は、.resizable をつけることで、親 View から渡された大きさに調整して表示されます。
Image 以外では?
一般に使える view modifier として、.scaleEffect という view modifier が用意されています。
func scaleEffect(_ scale: CGSize, anchor: UnitPoint = .center) -> some View
縦横のスケール値と スケール時の中心点を指定することができます。(異なる条件を使って設定する別の scaleEffect も用意されています。)
scaleEffect で良いのでは?
scaleEffect を使うと、Image だけではなく、さまざまな View をスケーリングして調整できるのですが、スケール値を具体的に指定する必要がある点が、ポイントです。
「本来の2倍の大きさで表示」というような要求であれば、ぴったりの view modifier です。以下のように使うことで実現できます。
MyView()
.scaleEffect(CGSize(width: 2.0, height: 2.0))
しかし、通常の SwiftUI の方法では、子ビューの実際のサイズ(親 View が提案したサイズではなく、子 View が実際に使用するサイズ)を取得することはできないため、
「iPhone の各機種ごとに異なる解像度の画面いっぱいに表示したい」というような要求に対しては、そのまま使うことはできません。
MyView()
.scaleEffect(CGSize(width: ??, height: ??))
MyView() の サイズは、この時点では決定していませんので、.scaleEffect に数値を与えることができません。
Preference
子 View から、親 View に情報を伝達する方法として、Preference というものが用意されています。
詳細は別 Post で説明する予定ですが、Preference を使用することで、子 View から親 View へ情報を渡すことができます。
# .navigationTitle なども 同様に Preference の仕組みを使っていると考えられます。
自 View のサイズを渡す Preference
以下のような Preference を使用することで、自分のサイズを親 View に渡すことができます。
struct SizePreferenceKey: PreferenceKey {
typealias Value = [SizePreferenceData]
static var defaultValue: [SizePreferenceData] = []
static func reduce(value: inout [SizePreferenceData], nextValue: () -> [SizePreferenceData]) {
value.append(contentsOf: nextValue())
}
}
struct SizePreferenceSetter: View {
let sizeName: String
var body: some View {
GeometryReader { geom in
Color.clear
.preference(key: SizePreferenceKey.self, value: [SizePreferenceData(name: sizeName, size: geom.size)])
}
}
}
自分のサイズを Preference に設定したいと思っているビューに、以下のように設定します。
MyView()
.background(SizePreferenceSetter(sizeName: "targetSize"))
上記のようにすることで、Preference が設定されるようになり、Preference に設定された値は 以下のように view modififer を使用して取得することができます。
.onPreferenceChange(SizePreferenceKey.self, perform: { prefs in
for pref in prefs {
if pref.name == "targetSize" {
// process pref.size
}
}
})
ビューのサイズを設定して取得して処理する View Modifier
先の Preference は、自分のサイズを取得することにも使用できます。
以下の View Modifier は、自分のサイズを取得して、指定されたサイズにあうように .scaleEffect を使用して View を拡大縮小するものです。
# 以下のコードでは、min(1.0, scale) とすることで、最大スケール値を 1.0 つまり 拡大は行わないようにしています。
struct Scale: ViewModifier {
let size: CGSize
@State var horizontalScale: CGFloat = 1.0
@State var verticallScale: CGFloat = 1.0
public init(_ width: CGFloat = -1,_ height: CGFloat = -1) {
self.size = CGSize(width: width, height: height)
}
func body(content: Content) -> some View {
content
.background(SizePreferenceSetter(sizeName: "targetSize"))
.scaleEffect(CGSize(width: min(1.0, horizontalScale), height: min(1.0, verticallScale)))
.onPreferenceChange(SizePreferenceKey.self, perform: { prefs in
for pref in prefs {
if pref.name == "targetSize" {
self.horizontalScale = self.size.width == -1 ? 1.0 : self.size.width / pref.size.width
self.verticallScale = self.size.height == -1 ? 1.0 : self.size.height / pref.size.height
}
}
})
}
}
調整したい View に対して以下のように modifier として使用します。
以下では、画面サイズに収まるように ビューをスケール表示しています。
var body: some View {
MyView()
.scale(screenWidth * 1.0, screenHeight * 1.0)
}
var screenWidth: CGFloat {
return UIScreen.main.bounds.size.width
}
var screenHeight: CGFloat {
return UIScreen.main.bounds.size.height
}
使用例:増えてきている画面サイズへの対応
iPhone の最初の頃は、画面サイズは 1サイズで機種依存を考慮する必要がありませんでした。
しかし、最近では iPhoneSE(2nd) = 375×667 から iPhone 12 Pro Max = 428×926 まで、幅広くなってきて 1つのレイアウトで全機種対応することが難しくなってきました。
解像度の大きなスクリーンでは、その画面の大きさを活かして さまざまな情報を表示したくなるのですが、同じレイアウトでは、解像度の小さいスクリーンに収まらないケースが増えてきました。
特に SwiftUI では、子 View のサイズは、最終的に 各 子View が設定するため、制約ベースの時のような調整が難しい気がします。
今回作った .scale のようなものを使用して、スケールしてしまうのも 解決策の1つになります。
まとめ:View の大きさをスケーリングで変更する方法
- .scaleEffect を使用することでスケーリングすることができる
- 子 View からの情報は、Preference 経由で取得する必要がある
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link