iOSアプリ開発をしていて、Viewの配置についていろいろなやり方があるなーと思ってます。
一度基本的なViewの配置について整理してみたいと思います。
Contents
0.VStackとHStackとZStack
VStackはVertical Stackという名の通り、Vertical(垂直に)Stack(置く)
垂直に配置していく。
HStackはHorizontal Stackという名の通り、Horizontal(水平に)Stack(置く)
水平に配置していく。
ZStackはZ軸方向に配置していく。3D空間での軸の見方によりますが
VStackとHStackは2D空間内に配置するもので、それぞれY軸とX軸方向に配置します。
ZStackはそのZ軸つまり画面の手前に配置していくイメージです。
Viewの手前にViewを配置して、さらにViewの手前にまた別のViewを配置してという、上に重ねておいていくイメージになります。
1. VStack内で左寄せや右よせ
普通にVStackをすると、Vertical Stackなので、垂直に上から順にViewが配置される。
でも配置する感覚としては、
Spacer()を間に置くことで、上ずめやしたずめに配置するようになる。
Spacerは臨機応変に
struct OffsetPosition1:View{ var body: some View{ VStack(){ Text("Hello World!!") } } }
struct OffsetPosition1:View{ var body: some View{ VStack(){ Spacer() Text("Hello World!!") } } }
VStackでそのVStackの真ん中に対象Viewがデフォルトで配置される。
VStackはあくまで縦の並び順を定義するものです。なのでVStackのalignmentは左寄せにするか、右寄せにするか、または真ん中に配置するかを指定する。
逆に、
HStackはあくまで横の並び順を定義するものなので、HStackのalignmentは上寄せにするか、下寄せにするか、または真ん中に配置するかを指定する。
これを真ん中ではなく、左よせ、右よせに配置したい場合は、それぞれ、.leading、.
を指定する。
.topLeading | .top | .topTrailing |
.leading | .center | .trailing |
.bottomLeading | .bottom | .bottomTrailing |
2. 対象Viewのframeを大きくしてその中で配置を行う
TextのようなViewはデフォルトでViewのサイズ(フレーム)は決まる
基本的にはフレーム内にピッタリテキストが埋まるイメージ。
少し大きめにこのTextの場所を画面上で確保した上で、テキストの位置を変更したい場合は、frameを大きくして、文章周りの余白が大きくなった上で、
alignmentで端に寄せるような形をする。
struct OffsetPosition1:View{ var body: some View{ VStack(){ Text("topTrailing!!") .frame(width: 400, height: 200, alignment: .topTrailing) .border(Color.black, width: 1) Text("center!!") .frame(width: 400, height: 200, alignment: .center) .border(Color.black, width: 1) Text("bottomLeading!!") .frame(width: 400, height: 200, alignment: .bottomLeading) .border(Color.black, width: 1) } } }
.topLeading | .top | .topTrailing |
.leading | .center | .trailing |
.bottomLeading | .bottom | .bottomTrailing |
3. 1つの起点(親View)からのpositionとoffset
1つのViewから少し下に別のViewを配置したいなどがあるとする。
常にそのViewの右下あたりに配置したいとかがある場合に、1つ基準点を立てた上でそこから何ピクセルか移動させて配置するというやり方がある。
offsetは移動した後の背景に対して処理
positionは移動する前の背景に対して処理
を行う
struct OffsetPosition:View{ var body: some View{ Text("Hello World!") .offset(x: 50, y: 50) .background(Color.yellow) Divider() Text("Hello World!") .position(x: 50, y: 50) .background(Color.yellow) } }
4. paddingによる配置
これはWebでもあるような、ボックスモデルのpadding。
botstrapでは、top、right、bottom、leftの4種類であるが、SwiftUIでは以下のように9つある。
VStackで定義できる、alighment
struct PaddingTest:View{ var body: some View{ VStack(){ ScrollView{ Text("1. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("2. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("3. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("4. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("5. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("6. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("7. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("8. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("9. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Text("10. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) } } } }
paddingによって隣接するViewの上に重なる訳ではなく、他のViewも押し出される形で配置される。同じレイヤーのViewではpaddingをしてもそのviewも押し出される形となる。
struct PaddingTest:View{ var body: some View{ VStack(){ ScrollView(){ Group{ Text("1. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("2. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("3. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("4. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() } Group{ Text("5. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("6. Hello World!!").frame(height: UIScreen.main.bounds.height/10).background(Color.green).border(Color.black,width: 1).padding(.top, 50) Divider() Text("7. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("8. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() Text("9. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) Divider() } Text("10. Hello World!!").frame(height: UIScreen.main.bounds.height/10).border(Color.black,width: 1) } } } }
5. GeometryReaderによる配置
配置を考えていく上で、さまざまなViewの配置情報を取得することは大事です。
例えば、あるViewを使い回す場合がよくあるかと思います。
そのViewはデフォルトでは全体Viewで使用するスクロールViewだが、他で使う場合は全体のうちの2/3のスペースで使いたいなどがあるとします。そこに埋め込むとなると
デフォルトが全体Viewのものを入れることができません。
なので、配置をしたいと考えているViewの大きさを取得し、その情報で配置することを考える必要があります。
Geometryから取得できる情報としては以下があります。
GeometryReaderViewの大きさ
geometry.size.width, geometry.size.height
対象のViewの真ん中のサイズ
geometry.size.frame(in: ,global).width
geometry.size.frame(in: ,global).height
globalは画面全体から見た場合の座標。
localはgeometryReaderを宣言したView全体から見た場合の座標。
なので、geometryが右下あたりで宣言した場合は、