[WWDC2023] iOS17におけるScrollViewの新機能 その1
iOS 17では多くの新しいScrollView
モディファイヤが追加され、アプリ開発体験を大幅に向上させることを約束している。その新機能を使ってみて、どのように動作するかを本記事で示す。
以下で4つのモディファイヤについて詳しく説明する。
.containerRelativeFrame(_:alignment:)
.scrollTargetLayout(isEnabled:)
.scrollTargetBehavior(_:)
.scrollPosition(id:)
もしこの記事が気に入ったら、ぜひいいねやフォローをお願いします。
それでは、始めよう!
.containerRelativeFrame(_:alignment:)
1. この新しいモディファイヤは、ビューの高さ、幅、または両方をコンテナビューに関連して指定することができる。例では、ZStack
にこれを使用している。ここで、その高さと幅をコンテナビュー、この場合はScrollView
に対して設定している。
struct ScrollExamplePaging: View {
var body: some View {
ScrollView(.horizontal) {
let strings: [String] = ["1", "2", "3", "4", "5"]
LazyHStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack {
// ...
}
.padding(16)
.containerRelativeFrame(.horizontal)
.containerRelativeFrame(.vertical)
}
}
}
}
}
以下のように簡略化することができる:
.containerRelativeFrame([.vertical, .horizontal])
そして、結果は以下のようになる:
このモディファイヤはScrollView
だけでなく、他のコンテキストでも使用できるようだ。Appleのドキュメンテーションによると、以下の状況で使うことができる:
- iPadOSまたはmacOSでビューを表示するウィンドウ、またはiOSのデバイスの画面。
- NavigationSplitViewの列
- NavigationStack
- TabViewのタブ
- ScrollViewやListのようなスクロール可能なビュー
.scrollTargetLayout(isEnabled:)
2. このスクロールターゲットレイアウトモディファイヤは、ScrollView
内のメインビュー(コンテナビュー)、この場合はZStack
に添付される。これにより、ScrollViewはどこに揃えるべきかを決定できる。これにより、特定のScrollView
のスクロールターゲットが設定される。デフォルトでは、この設定はtrueに設定されている
struct ScrollExamplePaging: View {
var body: some View {
ScrollView(.horizontal) {
let strings: [String] = ["1", "2", "3", "4", "5"]
LazyHStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack {
// ...
}
.padding(16)
.containerRelativeFrame([.vertical, .horizontal])
}
}
.scrollTargetLayout()
}
}
}
このモディファイヤ自体はビューに影響を与えない、それを3.と4.のモディファイヤと組み合わせて使用しないといけない。
.scrollTargetBehavior(_:)
3. それでは、セクション2でのターゲット設定から、ScrollView
の動作を定義する段階へと移ろう。これは、ScrollViewがどのように機能するかを決定することを意味する。これには、.paging
や.viewAligned
のようなScrollView
の動作を選択する作業が含まれる。それぞれについて詳しく見ていこう。
.paging
.paging
動作は、ScrollView
をページングインターフェースに変換する。これはTikTokの縦スクロールやスムーズなオンボーディング体験を思い浮かべてみてください。この動作は、ビューの高さと幅を利用して次のページへスムーズに遷移し、ビューの一部が切り取られることなく常にフルビューが表示されるようにする。
.paging
を実装するとき、この動作がコンテナビューが全画面幅を占めることを要求するため、LazyHStackのスペースを0に設定することが重要です。
スペースが16に設定されている場合(ページング動作が乱れている)
スペースが0に設定されている場合(通常のページング動作)
コード:
struct ScrollExamplePaging: View {
var body: some View {
ScrollView(.horizontal) {
let strings: [String] = ["1", "2", "3", "4", "5"]
LazyHStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack {
RoundedRectangle(cornerRadius: 16)
.fill(.black.gradient)
Text(string)
.font(.system(size: 92))
.fontWeight(.bold)
.foregroundStyle(.white)
}
.padding(16)
.containerRelativeFrame([.vertical, .horizontal])
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
}
}
.viewAligned
スペースを加えたり、より複雑なレイアウトを作成したい場合には、.viewAligned動作が便利です。これにより、ScrollViewの開始位置をカスタマイズして、スクロール時にコンテナビューとスムーズに整列することができる。この機能は、レイアウトに柔軟性を追加する。
こんな感じです:
コード:
struct ScrollExamplePaging: View {
var body: some View {
ScrollView(.horizontal) {
let strings: [String] = ["1", "2", "3", "4", "5"]
HStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack {
RoundedRectangle(cornerRadius: 16)
.fill(.black.gradient)
Text(string)
.font(.system(size: 92))
.fontWeight(.bold)
.foregroundStyle(.white)
}
.frame(width: 300)
.containerRelativeFrame(.vertical)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
}
}
.scrollPosition(id:)
4. ScrollView
に表示されている項目を追跡することができます。これを使うのはとても簡単です。@State
変数を作成します:@State private var scrollPosition
そして、この変数をモディファイアにバインドします: .scrollPosition(id: $scrollPosition)
。これにより、スクロールするとScrollView
は表示している値を追跡する。つまり、この値を他のビューにリンクすることができる。
こんな感じです:
コード:
struct ScrollViewExampleScrollPosition: View {
@State private var scrollPosition: String?
let strings: [String] = ["1", "2", "3", "4", "5"]
var body: some View {
GeometryReader { geo in
let size = geo.size
VStack {
ScrollView(.horizontal) {
HStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 16)
.fill(.black.gradient)
Text(string)
.font(.system(size: 92))
.fontWeight(.bold)
.foregroundStyle(.white)
}
.frame(width: 300, height: 500)
.padding(.vertical, 16)
.padding(.horizontal, (size.width - 300) / 2)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $scrollPosition)
VStack {
if let scrollPosition {
Text(scrollPosition)
.font(.largeTitle)
}
}
}
}
}
}
または、ボタンを使用して、表示したい番号に直接ジャンプすることもできる。
こんな感じ:
コード:
struct ScrollViewExampleScrollPosition: View {
@State private var scrollPosition: String?
let strings: [String] = ["1", "2", "3", "4", "5"]
var body: some View {
GeometryReader { geo in
let size = geo.size
VStack {
ScrollView(.horizontal) {
HStack(spacing: 16) {
ForEach(strings, id: \.self) { string in
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 16)
.fill(.black.gradient)
Text(string)
.font(.system(size: 92))
.fontWeight(.bold)
.foregroundStyle(.white)
}
.frame(width: 300, height: 500)
.padding(.vertical, 16)
.padding(.horizontal, (size.width - 300) / 2)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $scrollPosition)
VStack {
ForEach(strings, id: \.self) { string in
Button("Scroll to \(string)") {
withAnimation {
scrollPosition = string
}
}
.buttonStyle(.borderedProminent)
}
}
}
}
}
}
ここまで読んでくれてありがとうございました。
その2楽しみにしてください!
Spacely, Inc. App Div.
Dean Thompson
Follow me!
Twitter
Discussion