LoginSignup
3
2

More than 1 year has passed since last update.

NCMBのSwift SDKを使ってカレンダーアプリを作る(その1:画面の説明とSDKの導入)

Last updated at Posted at 2022-11-22

NCMBのSwift SDKを使ってカレンダーアプリを作ります。予定を登録したり、カレンダーコンポーネント(FSCalendar)を使って予定を表示できるというアプリです。

まず最初の記事では画面の説明とSDKの導入までを進めます。

コードについて

今回のコードはNCMBMania/swift-calendar-appにアップロードしてあります。実装時の参考にしてください。

利用技術について

今回は次のような組み合わせになっています。

仕様について

今回はNCMBのID/パスワード認証を利用しています。カレンダー表示についてはFSCalendarを利用し、アプリはSwiftUIで作成しています。

利用する機能について

カレンダーアプリで利用するNCMBの機能は次の通りです。

  • 会員管理
    • ID/パスワード認証
  • データストア
    • 予定の登録・更新・削除
    • 予定の検索

画面について

今回は以下の5つのViewがあります。

ContentView

メインのViewです。認証状態によってログイン画面とカレンダー画面を出し分けています。

struct ContentView: View {
    @State var isLogin: Bool = NCMBUser.currentUser != nil
    var body: some View {
        // ログイン判定
        if isLogin {
            // ログイン済みの場合
            CalendarView();
        } else {
            // 未ログインの場合
            LoginView(isLogin: $isLogin);
        }
    }
}

LoginView

Simulator Screen Shot - iPhone 14 Pro - 2022-11-22 at 10.50.19.png

認証を行うViewです。認証状態をContentViewにフィードバックしています。

struct LoginView: View {
    // 入力用
    @State private var userName: String = ""
    @State private var password: String = ""
    // レスポンス用
    @Binding var isLogin: Bool

    var body: some View {
        VStack(spacing: 16) {
            TextField("ユーザ名", text: $userName)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: 280)
            SecureField("パスワード", text: $password)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: 280)
            Button(action: {
                _signUpOrLogin()
            }, label: {
                Text("新規登録/ログイン")
            })
        }
    }
    
    // ユーザー登録またはログインを行う処理
    func _signUpOrLogin() -> Void {
			// 省略
    }
}

CalendarView

Simulator Screen Shot - iPhone 14 Pro - 2022-11-22 at 10.50.33.png

カレンダー表示用のViewです。 ObservableObject を使ってカレンダーなどにデータを渡しています。

// カレンダー用の構造体
class DayData: ObservableObject {
    @Published var schedules: [NCMBObject] = []
    @Published var selectedDate = Date()
    @Published var currentYearMonth = Date()
}

struct CalendarView: View {
    @ObservedObject var dayData = DayData()
    // 作成・更新した際のフラグ
    @State private var updated = false
    // 削除されたオブジェクトIDを入れる
    @State private var deleteObjectId = ""
    // 作成したスケジュールが入るNCMBObject
    @State private var schedule: NCMBObject = NCMBObject(className: "Schedule")
    
    // 予定をNCMBのデータストアから取得する関数
    func _getSchedule() {
			// 省略
    }
    
    // 一覧のタイトル用
    func _viewTitle() -> String {
			// 省略
    }
    
    // 該当日のスケジュールだけを返す関数
    func _dateFilter() -> [NCMBObject] {
			// 省略
    }
    
    var body: some View {
        NavigationView {
            VStack {
                // カレンダービュー
                FSCalendarView(dayData: Binding.constant(dayData))
                    .frame(height: 400)
                    .onChange(of: dayData.currentYearMonth, perform: {(newValue) in
                        // 表示月が変わったら、予定を取得し直す
                        _getSchedule()
                    })
                Text(_viewTitle())
                    .font(.title)
                    .padding()
                // 選択した日の予定を一覧表示
                List(_dateFilter(), id: \.objectId) { schedule in
                    // 一覧をタップしたら、編集画面に遷移
                    NavigationLink(destination: FormView(schedule: Binding.constant(schedule), updated: $updated, deleteObjectId: $deleteObjectId)) {
                        // 一覧表示用
                        CalendarListItemView(schedule: schedule)
                    }
                }
                Spacer()
            }
            .navigationTitle("カレンダー")
            // ナビゲーションバーのプラスアイコン
            .navigationBarItems(trailing:
                                    NavigationLink(destination: FormView(schedule: $schedule, updated: $updated, deleteObjectId: $deleteObjectId), label: {
                    Image(systemName: "plus")
                })
            )
            // 予定を追加、更新した際のイベント
            .onChange(of: updated, perform: {_ in
                if updated {
                    // 予定を追加
                    if schedule.objectId != nil {
                        dayData.schedules.append(schedule)
                        schedule = NCMBObject(className: "Schedule")
                    }
                    // フラグを落とす
                    updated = false
                }
            })
            // 予定を削除された際のイベント
            .onChange(of: deleteObjectId, perform: {_ in
                if deleteObjectId != "" {
                    // 予定データから削除された予定を削除
                    dayData.schedules.removeAll(where: {
                        $0.objectId == deleteObjectId
                    })
                    // 削除されたオブジェクトIDをリセット
                    deleteObjectId = ""
                }
            })
        }
        // 表示された際にスケジュールを取得する
        .onAppear {
            _getSchedule()
        }
    }
}

FSCalendarView

FSCalendarを操作するためのViewです。【SwiftUI】FSCalendarの実装方法【イベント日表示と選択日表示の方法】を参考にさせてもらっています。

// カレンダービュー
struct FSCalendarView: UIViewRepresentable {
    @Binding var dayData: DayData
    func makeUIView(context: Context) -> FSCalendar {
        typealias UIViewType = FSCalendar
        let fsCalendar = FSCalendar()
        fsCalendar.delegate = context.coordinator
        fsCalendar.dataSource = context.coordinator
        fsCalendar.appearance.headerDateFormat = "yyyy年MM月"
        
        return fsCalendar
    }
    // 再描画用
    func updateUIView(_ uiView: FSCalendar, context: Context) {
        uiView.reloadData()
    }
    
    func makeCoordinator() -> Coordinator{
        return Coordinator(self)
    }
    
    class Coordinator: NSObject, FSCalendarDelegate, FSCalendarDataSource {
        var parent: FSCalendarView
        
        init(_ parent: FSCalendarView){
            self.parent = parent
        }
        
        // 予定がある日付にドットを表示する処理
        func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
            // データがない場合は0を返して終わり
            if (parent.dayData.schedules.isEmpty) {
                return 0;
            }
            // 該当日のデータだけを抽出
            let events: [NCMBObject] = parent.dayData.schedules.filter({(obj) in
                if obj.objectId == nil {
                    return false
                }
                let startDate = obj["startDate"]! as Date
                let targetDate = Calendar.current.dateComponents([.calendar, .year, .month, .day], from: startDate).date
                return date.compare(targetDate!) == .orderedSame
            })
            // 該当日のカウントを返す
            return events.count
        }
        
        // 日付を選択した際の処理
        func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
            parent.dayData.selectedDate = date
          
        }
        
        // 年月を変更した際の処理
        func calendarCurrentPageDidChange(_ calendar: FSCalendar) {
            parent.dayData.currentYearMonth = calendar.currentPage
        }
        
    }
}

CalendarListItemView

カレンダーで選択された日付にある予定を一覧表示する際に利用するViewです。CalendarListItemViewは各行あたりのViewを担当します。

// 予定の一覧用(行)
struct CalendarListItemView: View {
    @State var schedule: NCMBObject
    // 時間を表示する文字列を返す
    func _viewTime() -> String {
			// 省略
    }

    // 描画
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text(schedule["title"]! as String)
                    .fontWeight(.bold)
                    .font(.title3)
                    .padding(.leading, 20)
                Spacer()
                Text(_viewTime())
                    .frame(alignment: .trailing)
                    .font(.caption)
                    .padding(.trailing, 20)
            }
            Text(schedule["body"]! as String)
                .padding(.horizontal, 20)
                .padding(.top, 5)
                .padding(.bottom, 10)
        }
        .frame(maxWidth: .infinity)
    }
}

FormView

Simulator Screen Shot - iPhone 14 Pro - 2022-11-22 at 10.50.59.png

予定の入力・更新、削除を行うViewです。

struct FormView: View {
    // 一覧から受け取った予定
    @Binding var schedule: NCMBObject
    // 作成・更新用フラグ
    @Binding var updated: Bool
    // 削除用のオブジェクトIDを入れる
    @Binding var deleteObjectId: String
    
    @Environment(\.presentationMode) var presentation
    // 入力用
    @State private var _title: String = ""
    @State private var _body: String = ""
    @State private var _startDate: Date = Date.now
    @State private var _endDate: Date = Date.now
    
    // スケジュールデータを入力用に適用される処理
    func _setValue() -> Void {
			// 省略
    }
    
    // 開始日が変更されたら、それに合わせて終了日を自動設定
    func _setEndDate() -> Void {
			// 省略
    }
    
    // スケジュールの保存処理
    func _save() -> Void {
			// 省略
    }
    // 予定の削除処理
    func _delete() -> Void {
			// 省略
    }
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("予定のタイトル", text: $_title)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: .infinity)
                .padding(.horizontal, 20)
            DatePicker("開始日時", selection: $_startDate)
                .padding(.horizontal, 20)
            DatePicker("終了日時", selection: $_endDate)
                .padding(.horizontal, 20)
            TextEditor(text: $_body)
                .frame(maxWidth: .infinity, maxHeight: 300)
                .border(.gray)
                .padding(.horizontal, 20)
            Button(action: {
                _save()
            }, label: {
                Text("新規保存 or 更新")
            })
            _schedule.wrappedValue.objectId != nil ?
            Button(action: {
                _delete()
            }, label: {
                Text("予定の削除")
            }) : nil
        // 開始日が変更された際のイベント
        }.onChange(of: _startDate, perform: {(_) in
            _setEndDate()
        })
        // 表示された際のイベント
        .onAppear {
            _setValue()
        }
    }
}

SDKのインストール

今回はFSCalendarがCocoaPods向けにしか対応していないので、NCMB SDKについてもCocoaPods経由でインストールしています。Podfileの内容です。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'calendar' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for calendar
  pod 'FSCalendar'
  pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
  target 'calendarTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'calendarUITests' do
    # Pods for testing
  end

end

後は pod install を実行してインストールは完了です。

NCMBのAPIキーを取得

mBaaSでサーバー開発不要! | ニフクラ mobile backendにてアプリを作成し、アプリケーションキーとクライアントキーを作成します。

calendarApp.swiftの修正

〜App.swiftファイルを開いて、NCMB SDKの読み込みと初期化を行います。

import SwiftUI
import NCMB

@main
struct calendarApp: App {
    init() {
        // NCMBの初期化
        NCMB.initialize(applicationKey: "YOUR_APPLICATION_KEY", clientKey: "YOUR_CLIENT_KEY")
    }
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

これでNCMBの初期化が完了します。

まとめ

今回はカレンダーアプリの仕様と画面、NCMBの初期化までを解説しました。次はログイン処理を実装します。

3
2
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
3
2