スポンサーリンク

[SwiftUI]カード(影つき)をスクロールする方法

スポンサーリンク

最近はカード型のViewが流行っているようです。なんとなくおしゃれな感じするしね。
SwiftUIで、カードのViewをスクロールする方法はいくつかあるので、縦スクロール横スクロールについてまとめておきます。

今回はContentView.swiftファイルしか触りません。

開発環境バージョン
Xcode12.5.1
iOS14.0以降
macOSBigSur 11.5.2
スポンサーリンク

ScrollViewでカード型Viewを横スクロールする

まずは横スクロール。
ただの白いカードじゃつまらないのでトランプ風カードのViewを作ってみました。

もうちょっとなめらかな動きです
//
//  ContentView.swift
//  CardApp
//
//  Created by Yaguchi Sato on 2021/12/07.
//

import SwiftUI

struct ContentView: View {
  var body: some View {
//横スクロール
      ScrollView(.horizontal){
//表示するやつだけ順番に読み込む
       LazyHStack{
//スクロール方法を変えても共通のコード↓
        ForEach(1...10,id: \.self){num in
            VStack{
                //左上の数字
                HStack {
                    Text("\(num)")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .padding()
                    Spacer()
                }
                Spacer()
                //numの数だけハートを表示
                ForEach(1...num, id:\.self){_ in
                    Image(systemName:"heart.fill")
                        .resizable()
                        .foregroundColor(Color.red)
                        .scaledToFit()
                        .frame(width: 30, height: 30, alignment: .center)
                }
                Spacer()
                HStack {
                    Spacer()
                    //右下の数字
                    Text("\(num)")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .padding()
                }
            }
            //トランプの形
                .frame(
                    width:UIScreen.main.bounds.size.width * 0.8,
                    height:UIScreen.main.bounds.size.height * 0.6
                )
                .background(
                    RoundedRectangle(cornerRadius: 15)
                            .fill(Color(.white))
                            //影をつける
                            .shadow(radius: 10)
                )
//スクロール方法を変えても共通のコード↑

     //トランプの最初の1枚がセンターにくるように余白を設定
               .padding(.leading, (UIScreen.main.bounds.size.width * 0.1))
        }
       }
      }
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

ScrollView(.horizontal)で横スクロールになります。
その中にLazyHStackを配置。
Lazyにするとカードを一度にではなく表示するときに読み込んでくれるので、メモリの負担を軽減できるらしい。

ScrollViewで真ん中でピタッと止まる、というような機能は今のところつけられないみたいです。
とりあえずスタートが真ん中になるように、カードの横幅サイズから余白を計算して設定してます。
(カード横幅を画面横幅*0.8にしたので、カード左横の余白を画面横幅*0.2/2に設定。)

次はトランプ風カードはそのままで縦スクロールに変更してみます。

ScrollViewでカード型Viewを縦スクロールする

実際はもうちょっとなめらかな動きです
//
//  ContentView.swift
//  CardApp
//
//  Created by Yaguchi Sato on 2021/12/07.
//

import SwiftUI

struct ContentView: View {
  var body: some View {
//縦方向に変更
      ScrollView(.vertical){
       LazyVStack{
//スクロール方法を変えても共通の部分↓
        ForEach(1...10,id: \.self){num in
            VStack{
                //左上の数字
                HStack {
                    Text("\(num)")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .padding()
                    Spacer()
                }
                Spacer()
                //numの数だけハートを表示
                ForEach(1...num, id:\.self){_ in
                    Image(systemName:"heart.fill")
                        .resizable()
                        .foregroundColor(Color.red)
                        .scaledToFit()
                        .frame(width: 30, height: 30, alignment: .center)
                }
                Spacer()
                HStack {
                    Spacer()
                    //右下の数字
                    Text("\(num)")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                        .padding()
                }
            }
            //トランプの形
                .frame(
                    width:UIScreen.main.bounds.size.width * 0.8,
                    height:UIScreen.main.bounds.size.height * 0.6
                )
                .background(RoundedRectangle(cornerRadius: 15)
                            .fill(Color(.white))
                            //影をつける
                            .shadow(radius: 10))
//スクロール方法を変えても共通の部分↑
            //縦方向の余白に変更
            .padding(.top, (UIScreen.main.bounds.size.height * 0.2))
        }
       }
      }
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

横スクロールから変えたのは、ScrollView(.vertical)と、LazyVStackです。
vertical=縦方向
VStack=縦に積む
という感じです。

あとはカード外側の余白「.padding」を(.top, (UIScreen.main.bounds.size.height * 0.2))に設定して、最初の一枚が画面の真ん中からスタートするようにしてます。

(カードの高さheightを画面サイズ*0.6に設定しているので、その残り0.4の半分の高さの余白をカード上部に設定。)

こちらも同じScrollViewなので、真ん中でピタッと止まる、というような機能はありません。

Listでカード型Viewを縦スクロールする

gifだとわかりにくいけどカード下にリストの区切り線が入ってる
//
//  ContentView.swift
//  CardApp
//
//  Created by Yaguchi Sato on 2021/12/07.
//

import SwiftUI

struct ContentView: View {
  var body: some View {
//Listで縦スクロールを実装
        List{
    //スクロール方法を変えても共通の部分↓
            ForEach(1...10,id: \.self){num in
                VStack(alignment: .center){
                    //左上の数字
                    HStack {
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                        Spacer()
                    }
                    Spacer()
                    //numの数だけハートを表示
                    ForEach(1...num, id:\.self){_ in
                        Image(systemName:"heart.fill")
                            .resizable()
                            .foregroundColor(Color.red)
                            .scaledToFit()
                            .frame(width: 30, height: 30, alignment: .center)
                    }
                    Spacer()
                    HStack {
                        Spacer()
                        //右下の数字
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                    }
                }
                //トランプの形
                    .frame(
                        width:UIScreen.main.bounds.size.width * 0.8,
                        height:UIScreen.main.bounds.size.height * 0.6
                    )
                    .background(RoundedRectangle(cornerRadius: 15)
                                .fill(Color(.white))
                                //影をつける
                                .shadow(radius: 10))
    //スクロール方法を変えても共通の部分↑
      .padding(.leading, 20.0)
                .padding(.top, (UIScreen.main.bounds.size.height * 0.2))
            }
        }    
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


リストでの縦スクロールのいいところは、自動的にLazyになるところです。
多分メモリ的な負担は一番少ないんだけど、中央揃えにならなくてレイアウトの調整がめんどい。
(paddingで誤魔化してるけど中央揃えになってるわけじゃないので注意。)

あとカードの下にリストの仕切り線が入っちゃうのがちょっと気になる。
消せなくはないんだけど、無理矢理感があって個人的に好きじゃないので今回はそのままにしてます。

カード型ならScrollViewの方がレイアウトはしやすいかも。

ScrollViewReaderでカード型Viewを横スクロールする

真ん中でピタッと止まるスクロールでは、DragGestureによるスクロール方法をよく見かけます。
よく見かけるので、今回は別の方法を試みようと思い、iOS14から使える「ScrollViewReader」を使ってみました。

このgifよりももうちょっとスクロール感ある動き方をします
//
//  ContentView.swift
//  CardApp
//
//  Created by Yaguchi Sato on 2021/12/07.
//

import SwiftUI

struct ContentView: View {
  var body: some View {
//ListやGridLayoutでも使用可能なscroll制御です
    ScrollViewReader{proxy in
    ScrollView(.horizontal){
        LazyHStack{
    
            ForEach(1...10, id: \.self){num in
                VStack(alignment: .center){
                VStack{
                    //左上の数字
                    HStack {
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                        Spacer()
                    }
                    Spacer()
                    //numの数だけハートを表示
                    ForEach(1...num, id:\.self){_ in
                        Image(systemName:"heart.fill")
                            .resizable()
                            .foregroundColor(Color.red)
                            .scaledToFit()
                            .frame(width: 30, height: 30, alignment: .center)
                    }
                    Spacer()
                    HStack {
                        Spacer()
                        //右下の数字
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                    }
                }.id(num)//スクロールする要素にnumと同じ数字をidとして付与
                
                //トランプの形
                    .frame(
                        width:UIScreen.main.bounds.size.width * 0.8,
                        height:UIScreen.main.bounds.size.height * 0.6
                    )
                    .background(RoundedRectangle(cornerRadius: 15)
                                .fill(Color(.white))
                                //影をつける
                                .shadow(radius: 10))
                    .padding(.horizontal,(UIScreen.main.bounds.size.width * 0.1))
                //矢印をタップで隣の数字へスクロール
                    Button("→") {
                        withAnimation{
     //飛び先はnumの次の数字
                            proxy.scrollTo(num + 1, anchor: .center)
                        }      
                    }
                    .font(.largeTitle)
                    .padding()
                }
            }
        }
    }
  }
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

矢印ボタンをタップすると自動でスクロールして真ん中で止まります。
手動でスクロールもできます。

スクロールする要素にidを付与して、指定したidにジャンプして表示できます。
今回はidをnumにして、1をプラスすることで隣の数字を指定。
表示位置はanchorで.center(真ん中)を指定しました。

左右のカードが少し見えるようにしたい時とか、最後まで進んだら戻るとか、もう少し工夫したらさらにいい動きにできそうな感じはしますよね。
(つきつめたら長くなりそうで、とりあえず今回はやめた。)

ScrollViewReaderは縦スクロールでも使えますよ。

【追記】TabViewでカードを横スクロールする

TabViewでページングができるって今更知ったので試してみました。追記。

インスタ的な動きをします
//
//  ContentView.swift
//  CardApp
//
//  Created by Yaguchi Sato on 2021/12/07.
//

import SwiftUI

struct ContentView: View {
  var body: some View {
   
        TabView{
//TabViewのページ要素がForEachで作るトランプ画像
            ForEach(1...10,id: \.self){num in
                VStack(alignment: .center){
                VStack{
                    //左上の数字
                    HStack {
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                        Spacer()
                    }
                    Spacer()
                    //numの数だけハートを表示
                    ForEach(1...num, id:\.self){_ in
                        Image(systemName:"heart.fill")
                            .resizable()
                            .foregroundColor(Color.red)
                            .scaledToFit()
                            .frame(width: 30, height: 30, alignment: .center)
                    }
                    Spacer()
                    HStack {
                        Spacer()
                        //右下の数字
                        Text("\(num)")
                            .font(.largeTitle)
                            .fontWeight(.bold)
                            .padding()
                    }
                }
                //トランプの形
                    .frame(
                        width:UIScreen.main.bounds.size.width * 0.8,
                        height:UIScreen.main.bounds.size.height * 0.6
                    )
                    .background(RoundedRectangle(cornerRadius: 15)
                                .fill(Color(.white))
                                //影をつける
                                .shadow(radius: 10))
   
                .padding(.horizontal,(UIScreen.main.bounds.size.width * 0.1))
               
                }
            }
        }
//ページングできるTabViewのスタイルを指定
        .tabViewStyle(PageTabViewStyle()) 
//PageControl(ページ送りの丸ぽち)のスタイル指定
//.alwaysを.neverにするとPageControlは非表示になる
        .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
 
  }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

勝手に真ん中でピタッと止まるし画像のスクロールにとてもいいのでは……?!
アプリの初期起動での使い方説明などでページングしたい時にもとても使えると思います。

以上、スクロールするViewについてでした〜

タイトルとURLをコピーしました