![見出し画像](https://assets.st-note.com/production/uploads/images/95800693/rectangle_large_type_2_8b21f5ffc031877168ef3f0617ecf5b8.png?width=800)
SwiftUIのAlignment Guideについて調べてみた
Alignment Guideとは?
Alignmentとは要素の配置を指定する構造体で、HorizontalAlignmentとVerticalAlignmentを含む。そしてAlignment Guideは、基準のAlignmentからの位置(オフセット)を調整できるViewModifierで、コンテナビュー内でのレイアウトを指定できる。
Alignment Guideの基本的な使い方
Alignment Guideの定義は下記の通りで、第1引数に基準となるAlignment(.centerや.leadingなど)を指定し、第2引数にオフセットとなる値を計算して返却するクロージャーを渡す。なお、オフセットの計算において、クロージャーの引数であるViewDimensionsを使って、Viewのwidthやheight、ViewのAlignment Guide位置を取得できる。
// HorizontalAlignmentからの位置を調整する
func alignmentGuide(
_ g: HorizontalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat
) -> some View
// VerticalAlignmentからの位置を調整する
func alignmentGuide(
_ g: VerticalAlignment,
computeValue: @escaping (ViewDimensions) -> CGFloat
) -> some View
下記コードではHotizontalAlignment.centerからのオフセットを指定しており、それぞれのTagが指定された分だけ位置が調整されていることがわかる。
struct BasicSampleView: View {
var body: some View {
VStack(alignment: HorizontalAlignment.center) {
Color.red.frame(width: 1)
Tag("#AAA")
.alignmentGuide(HorizontalAlignment.center) { context in
// .centerからのオフセットをwidthの2倍とする
return context.width * 2
}
Tag("#BBBBBBBBBB")
.alignmentGuide(HorizontalAlignment.center) { context in
// .centerからのオフセットをTagのtrailingに合わせる
context[HorizontalAlignment.trailing]
}
Tag("#CCCCC")
.alignmentGuide(HorizontalAlignment.center) { context in
// .centerからのオフセットをTagのcenterに合わせる
context[HorizontalAlignment.center]
}
Tag("#DDDDDDDDDDDDDDD")
.alignmentGuide(HorizontalAlignment.center) { context in
// .centerからのオフセットを0とする
return 0
}
Tag("#EEEEEEEEEE")
.alignmentGuide(HorizontalAlignment.center) { context in
// .centerからのオフセットをwidthの-0.5倍とする
return -context.width * 0.5
}
Color.red.frame(width: 1)
}
.border(Color.gray)
.navigationTitle("Basic Sample")
}
}
![](https://assets.st-note.com/img/1673874991099-4Vx5OZRMrx.png)
Alignment Guideとoffsetの違い
alignmentGuide(_:computeValue:)以外にもViewの位置を調整するoffset(x:y:)があるが、公式ドキュメントのDiscussionにあるように、offsetはあくまでも表示内容をずらすのであって、View本来のサイズは変更されない。
Use offset(x:y:) to shift the displayed contents by the amount specified in the x and y parameters. The original dimensions of the view aren’t changed by offsetting the contents
下記コードではalignmentGuideとoffsetそれぞれで位置を調整しており、alignmentGuideの場合はコンテナビューも合わせて変化しているのに対して、offsetの場合は初期状態のままであることがわかる。
struct AlignmentGuideVsOffset: View {
var body: some View {
VStack(spacing: 40) {
VStack(alignment: HorizontalAlignment.center) {
Tag("#AAA")
.alignmentGuide(HorizontalAlignment.center) { context in
return context.width * 2
}
Tag("#BBBBBBBBBB")
.alignmentGuide(HorizontalAlignment.center) { context in
context[HorizontalAlignment.trailing]
}
Tag("#CCCCC")
.alignmentGuide(HorizontalAlignment.center) { context in
context[HorizontalAlignment.center]
}
}
.border(Color.gray)
VStack(alignment: HorizontalAlignment.center) {
Tag("#AAA")
.offset(x: -50, y: 0)
Tag("#BBBBBBBBBB")
.offset(x: -30, y: 0)
Tag("#CCCCC")
.offset(x: 40, y: 0)
}
.border(Color.gray)
}
.navigationTitle("Alignment Guide vs. Offset")
}
}
![](https://assets.st-note.com/img/1673875019515-1E5kZxvIiN.png)
Alignment Guideの応用例
Alignment Guideを応用すると「タグを配置する際に自動的に改行させる」ようなレイアウトを組むことができる。HStackだとタグが詰まってしまうが、この記事にあるようにZStackとAlignment Guideを組み合わせることで実現できる。ただし、クロージャー内で複雑な計算が必要となってしまう。
補足:iOS 16から使えるLayoutプロトコルを使えば、複雑なレイアウトを組みやすくなり、タグの自動改行もより簡単になる(こちらを参照)。
struct TagBreakView: View {
@State var tagList = ["#AAA", "#BBBBBBBBBB", "#CCCCC", "#DDDDDDDDDDDDDDD", "#EEEEEEEEEE"]
var body: some View {
GeometryReader { geometry in
generateTags(geometry)
}
.padding()
}
private func generateTags(_ geometry: GeometryProxy) -> some View {
var leading = CGFloat.zero
var top = CGFloat.zero
return ZStack(alignment: .topLeading) {
ForEach(tagList, id: \\.self) { tag in
Tag(tag)
.padding([.horizontal, .vertical], 4)
.alignmentGuide(.leading, computeValue: { context in
if abs(leading - context.width) > geometry.size.width {
// 改行の場合はleadingをリセットする
leading = 0
// topも積算する
top -= context.height
}
// 改行判定後に返却値を代入
let result = leading
if tag == tagList.last {
// 複数回計算されるためリセットする
leading = 0
} else {
// leadingを積算する (次の基準とするため返却値に積算させない)
leading -= context.width
}
return result
})
.alignmentGuide(.top, computeValue: { _ in
let result = top
if tag == tagList.last {
// 複数回計算されるためリセットする
top = 0
}
return result
})
}
}
.border(Color.gray)
.navigationTitle("Tag Break")
}
}
![](https://assets.st-note.com/img/1673875028304-j9xYy4Jgmm.png)
サンプルコード
参考
この記事が気に入ったらサポートをしてみませんか?