23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【SwiftUI】みんなの銀行のUIを再現してみた

Last updated at Posted at 2021-05-30

1.はじめに

2021/5/28にリリースされたみんなの銀行というサービスはご存知でしょうか。
こちらは国内初のデジタルバンクであり、銀行口座の開設から運用まで全てスマホで完結するというサービスです。

公式サイトはこちら

私も実際に口座を開設して使ってみましたが、
UIがいい意味で銀行らしくなく、とても現代的なデザインで素敵でした。
ということで今回はみんなの銀行のUIをSwiftUIで再現できるかという記事になります。
※私はSwiftUI初めて1ヶ月程度の初心者ですので実装についてもっとこうした方がいいなどあればご指摘いただけると幸いです。

最後にコード全て載せているので実際にコピペして使ってみてください!




2. みんな銀行のUI

本家のUIです。

Videotogif(1).gif




3.作ったもの

実際に作ったものがこちらです。
みんなの銀行_再現.gif

ベースとなるTabViewとNavigationView、上にスワイプでモーダルメニュー画面が表示されるところまで再現してみました。
画像は特に関係のないフリー素材をデモ用として使っています。




4. 設計

 大体の設計はこんな感じです。作成するのはTabViewで切り替える3画面とスワイプしたときに表示する1画面となります。

図1.png

 画面でみるとこんな感じです。ポイントとなる①〜④についてコードを記載していきたいと思います。
図3.png




5. 実装

① NavigationView

まずはベースのNavigationViewについてです、こちらは基本的な内容ですね。

ContentView.swift
struct ContentView: View {
    
    init() {
        UINavigationBar.appearance().shadowImage = UIImage()
        UINavigationBar.appearance().barTintColor = UIColor(.white)
    }
    
    var body: some View {
        NavigationView {
            // TabViewを記載していく
        }
     .navigationBarItems(
                    leading: Text("タイトル".font(.largeTitle).fontWeight(.black)
                    ,
                    trailing:
                        HStack {
                            // NavigationBarに表示するButtonを記載していく
                        }
                )
    }

3点ほど作成するにあたってつまずきました。

  • NavigationBarの色を変える

    下記のようにすると変えられるようです。
UINavigationBar.appearance().barTintColor = UIColor(.white)
  • NavigationBarとViewの境目線を消す

    shadowImageに空のイメージを入れると消えるようです。
UINavigationBar.appearance().shadowImage = UIImage()
  • NavigationBarのタイトルをでっかくする
    はじめnavigationTitleで指定しようとしてfontが思うように反映されず手こずりました。
     navigationVarItemにTextで突っ込んでしまえば解決です。
leading: Text("タイトル".font(.largeTitle).fontWeight(.black)



②TabView(PageTabViewStyle)を使ってViewを切り替え

 メイン画面についてはTabViewをつかっています。
 こちらはタブでの画面切り替えと画面のスワイプでの切り替え両方を実現するように考えました。

 調べましたがうまく実装してくれるのがなさそうでしたので、
 スワイプでの画面切り替えをTabViewのPageTabViewStyleで実現し、
 タブでの画面切り替えは自作しました。(④)

struct ContentView: View {
    @State private var selection = 0
・・・

    VStack {
                // *** ページタブビュー ***
                TabView(selection: $selection) {
                    WalletView()
                        .tag(0)
                    BankingView()
                        .tag(1)
                    RecordView()
                        .tag(2)
                }
                .accentColor(.white)
                .tabViewStyle(PageTabViewStyle())
                .animation(.easeInOut)
                .transition(.slide)
    }
・・・
}

tabViewStyle(PageTabViewStyle())を指定してあげるとスワイプでタブ切り替えを実現してくれます。

また、下記のように設定すると初期の表示時とタブ切り替え時にスライドアニメーションしてくれます。

                .animation(.easeInOut)
                .transition(.slide)



③ 上にスワイプするとメニュー画面をモーダル表示

ここの実装が一番悩みました。。
SwiftUIでこれ!といったものが見つけられず。(もしあったら教えてください)

今回の作り方としてはテキストViewで「_____」と表示してそこを上にスワイプしたときにメニュー画面を表示するとしました。
”上にスワイプしたとき”というのは下記の記事のコードを参考にさせていただきました。

/[SwiftUI] Swipeの検知と実装

Text("________")
                .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height / 100 * 7, alignment: .bottom)
                .gesture(DragGesture()
                            .onEnded({ value in
                                if (abs(value.translation.height) < 10) { return } // too small movement, ignore note: 10 is default value for minimumDistance
                                if (value.translation.height < 0 ) {
                                    
                                    print("swipe to up")
                                    self.isMenuShowing = true
                                } else if (value.translation.height > 0 ) {
                                    print("swipe to down")
                                    self.isMenuShowing = false                                }
                            })
                )
                .fullScreenCover(isPresented: $isMenuShowing) {
                    ModalMenuView(isMenuShowing: self.$isMenuShowing)
                }

こちらがメニュー画面を表示する処理になります。
状態変数isMenuShowingがtrueの時に表示されます。
こちらは.gestureで上スワイプをした時にtrueにします。

               .fullScreenCover(isPresented: $isMenuShowing) {
                    ModalMenuView(isMenuShowing: self.$isMenuShowing)
                }



④ 自作でタブ切り替え

画面下部にあるタブ切り替え部分は自前で実装します。
自前と言っても下記をほぼそのまま使わせていただきました。ありがとうございます。

SwiftUIのTabViewの問題に自作TabView(SwiftUI製)で対処する




6. おわりに

いかがでしたでしょうか。
まだまだ本家の完成度には及びませんが、実際のUIを真似してみるのはとてもいい勉強になりますね。

おまけにコードを全て載せているので興味のある方はご自身の環境で実行してみてください!



おまけ

ContentView.swift
import SwiftUI

struct ContentView: View {
    @State private var selection = 0
    @State private var nvBarTitle : [String] = ["Wallet", "Banking", "Record"]
    private let width = UIScreen.main.bounds.width
    private let heigth = UIScreen.main.bounds.height / 100 * 9
    
    
    init() {
        UITabBar.appearance().barTintColor = UIColor(.black)
        UITabBar.appearance().backgroundColor = UIColor(.white)
        UITabBar.appearance().unselectedItemTintColor = UIColor(.gray)
        
        UINavigationBar.appearance().shadowImage = UIImage()
        UINavigationBar.appearance().barTintColor = UIColor(.white)
        
    }
    
    var body: some View {
        NavigationView {
            VStack {
                // *** ページタブビュー ***
                TabView(selection: $selection) {
                    WalletView()
                        .tag(0)
                    BankingView()
                        .tag(1)
                    RecordView()
                        .tag(2)
                }
                .accentColor(.white)
                .tabViewStyle(PageTabViewStyle())
                .animation(.easeInOut)
                .transition(.slide)
                .navigationBarItems(
                    leading: Text(nvBarTitle[self.selection]).font(.largeTitle).fontWeight(.black)
                    ,
                    trailing:
                        HStack {
                            if self.selection == 0 {
                                Button(action: {}) {
                                    Image(systemName: "trash")
                                        .foregroundColor(.black)
                                }
                                Button(action: {}) {
                                    Image(systemName: "trash")
                                        .foregroundColor(.black)
                                    
                                }
                            }else if self.selection == 1 {
                                Button(action: {}) {
                                    Image(systemName: "trash")
                                        .foregroundColor(.black)
                                }
                            }
                        }
                )
                
                // *** 自作ナビゲーションタブ ***
                ZStack {
                    Rectangle()
                        .foregroundColor(Color.black)
                        .frame(width: self.width, height: self.heigth)
                    
                    HStack(spacing: self.heigth / 2) {
                        Spacer()
                        
                        Button(action: {
                            self.selection = 0
                        }) {
                            VStack {
                                Image(systemName: "doc")
                                    .foregroundColor(self.selection != 0 ? Color.gray : Color.white)
                                    .font(.system(size: self.heigth / 3, design: .rounded))
                            }
                        }
                        .padding(.bottom, 30)
                        
                        Spacer()
                        
                        Button(action: {
                            self.selection = 1
                        }) {
                            VStack {
                                Image(systemName: "waveform.path.ecg")
                                    .foregroundColor(self.selection != 1 ? Color.gray : Color.white)
                                    .font(.system(size: self.heigth / 3, design: .rounded))
                            }
                        }.padding(.bottom, 30)
                        
                        Spacer()
                        
                        Button(action: {
                            self.selection = 2
                        }) {
                            VStack {
                                Image(systemName: "folder.fill.badge.plus")
                                    .foregroundColor(self.selection != 2 ? Color.gray : Color.white)
                                    .font(.system(size: self.heigth / 3, design: .rounded))
                            }
                        }.padding(.bottom, 30)
                        
                        Spacer()
                    }
                }
            }.edgesIgnoringSafeArea(.bottom)
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
}
WalletView.swift

import SwiftUI
import SlideOverCard

struct WalletView: View {
    @State private var position = CardPosition.middle
    @State private var background = BackgroundStyle.solid
    @State private var isMenuShowing = false
    
    var body: some View {
        VStack(alignment: .leading) {
            Image("IMG01")
                .resizable()
                .scaledToFit()
                .padding(50)
                .border(Color.black)
            
            VStack(alignment: .leading) {
                Text("普通預金")
                Text("¥********").font(.largeTitle).fontWeight(.black)
                Text("残高を表示")
                
            }
            .padding(30)
            
            Text("***お知らせ***").font(.title3)
                .padding(30)
            
            Spacer()
            Text("________")
                .frame(maxWidth: UIScreen.main.bounds.width, maxHeight: UIScreen.main.bounds.height / 100 * 7, alignment: .bottom)
                .gesture(DragGesture()
                            .onEnded({ value in
                                if (abs(value.translation.height) < 10) { return } // too small movement, ignore note: 10 is default value for minimumDistance
                                if (value.translation.height < 0 ) {
                                    // swiped to left
                                    print("swipe to up")
                                    self.isMenuShowing = true
                                } else if (value.translation.height > 0 ) {
                                    // swiped to down
                                    print("swipe to down")
                                    self.isMenuShowing = false
                                }
                            })
                )
                .fullScreenCover(isPresented: $isMenuShowing) {
                    ModalMenuView(isMenuShowing: self.$isMenuShowing)
                }
        }
        
        
    }
}

struct WalletView_Previews: PreviewProvider {
    static var previews: some View {
        WalletView()
    }
}
BankingView.swift
import SwiftUI

struct BankingView: View {
    var body: some View {
        VStack {
            Image("IMG03")
                .resizable()
                .scaledToFit()
                .padding(50)

            Spacer()
        }
    }
}
RecordView.swift
import SwiftUI

struct RecordView: View {
    var body: some View {
        
        VStack {
            Image("IMG02")
                .resizable()
                .scaledToFit()
                .padding(50)

            Spacer()
        }
        
    }
}

23
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?