0
2

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.

Swift SDKを使ってカメラメモアプリを作る(その1:画面の仕様とSwift SDKの導入)

Last updated at Posted at 2021-06-23

ニフクラ mobile backend(NCMB)では各種言語向けにSDKを提供しています。その中で最も新しいSwift SDKについて、その使い方を紹介します。

今回から複数回に分けて、カメラメモアプリを作ってみます。Swift SDKの使い方はもちろん、NCMBの基本的な利用法を学ぶのに役立ててください。

なお、今回のコードはNCMBMania/camera_memo_swift: Swift SDKを使ったカメラメモアプリのコードですにアップしてあります。

カメラメモアプリの機能について

カメラメモアプリは、写真を撮影して、そのデータをファイルストアにアップロードします。そして別途メモを書き残すことで、写真とメモを紐付けて保存します。

利用する機能について

カメラメモアプリで利用するNCMBの機能は次の通りです。

  • 認証機能
    • ID/パスワードによるログイン
    • ログアウト
  • ファイルストア
    • ファイルアップロード
    • ファイルダウンロード
  • データストア
    • ファイルストアと紐付けたメモを保存

画面について

カメラメモアプリでは次の画面(View)を用意しています。

  • ContentView
    タブバーで2つの画面を読み込んでいます。1つはログインとアップロード画面(UploaderView)、もう1つは写真一覧画面(ImageView)です。
  • Imagepicker
    写真の撮影またはフォトライブラリから写真を選択します。
  • UploaderView
    タブバーから読み込まれます。ログイン画面やアップロード画面を表示します。
  • MemoView
    撮影または選択した写真を表示し、メモとともにNCMBへアップロードします。
  • LoginView
    NCMBへのユーザ登録、ログイン処理を行います。
  • ImageView
    NCMBへアップロードした写真を一覧表示します。
  • ModalView
    写真一覧をタップした際にモーダル表示します。
  • GridImageView
    写真一覧のグリッド表示用ビューです。

ContentView

タブバーで2つの画面を読み込んでいます。

struct ContentView: View {
    var body: some View {
        TabView {
            UploaderView()
                .tabItem {
                    VStack {
                        Image(systemName: "photo")
                        Text("Photo")
                    }
            }.tag(1)
            ImageView()
                .tabItem {
                    VStack {
                        Image(systemName: "rectangle.grid.2x2")
                        Text("Photos")
                    }
            }.tag(2)
        }
    }
}

Imagepicker

写真の撮影またはフォトライブラリから写真を選択します。コードは【SwiftUI】カメラ機能の実装方法【撮影画像とライブラリー画像の利用】から拝借しています。ありがとうございます。

UploaderView

ログイン画面やアップロード画面を表示します。ログインしていない場合は LoginView を表示します。

struct UploaderView: View {
    @State var imageData : Data = .init(capacity:0)
    @State var source:UIImagePickerController.SourceType = .photoLibrary
    @State var isImagePicker = false
    @State var isLogin: Bool = NCMBUser.currentUser != nil
    var body: some View {
            NavigationView{
                VStack(spacing:0){
                        ZStack{
                            Color.white
                                .edgesIgnoringSafeArea(.all)
                                .opacity(0.0)
                            NavigationLink(
                                destination: Imagepicker(show: $isImagePicker, image: $imageData, sourceType: source),
                                isActive:$isImagePicker,
                                label: {
                                    Text("")
                                })
                            VStack{
                                if isLogin {
                                    VStack(spacing:30){
                                        MemoView(imageData: $imageData)
                                        HStack(spacing:60){
                                            Button(action: {
                                                self.source = .photoLibrary
                                                self.isImagePicker.toggle()
                                            }, label: {
                                                Text("Photo library")
                                            })
                                            Button(action: {
                                                self.source = .camera
                                                self.isImagePicker.toggle()
                                            }, label: {
                                                Text("Take Photo")
                                            })
                                        }
                                        Button(action: {
                                          // ログアウト処理(略)
                                        }, label: {
                                            Text("ログアウト")
                                        })
                                    }
                                } else {
                                    LoginView(isLogin: $isLogin)
                                }
                            }
                        }
                        .gesture(
                            TapGesture()
                                .onEnded { _ in
                                    UIApplication.shared.closeKeyboard()
                                }
                        )
                
                }
                .navigationBarTitle("Home", displayMode: .inline)
            }
        .ignoresSafeArea(.all, edges: .top)
        .background(Color.primary.opacity(0.06).ignoresSafeArea(.all, edges: .all))
    }
}

MemoView

ログインしている場合に使われる写真とメモを表示する画面です。

struct MemoView: View {
    @Binding var imageData : Data
    @State private var text: String = ""
    @State private var uploaded = false
    
    var body: some View {
        if imageData.count != 0 {
            Image(uiImage: UIImage(data: self.imageData)!)
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(height: 250)
                .cornerRadius(15)
                .padding()
            TextField("メモ", text: $text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .frame(maxWidth: 280)
            Button(action: {
                // メモアップロード処理(後述)
            }, label: {
                Text("Upload")
            })
            .alert(isPresented: $uploaded, content: {
                Alert(title: Text("アップロード完了"), message: Text("写真をアップロードしました"), dismissButton: .default(Text("閉じる"))
                )
            })
            
        }
    }
}

LoginView

ユーザ登録およびログイン処理を実行します。

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() {
      // ユーザ登録とログイン処理(後述)
    }
}

ImageView

ファイルストアから画像を取得します。

struct ImageView: View {
    @State var memos: [NCMBObject] = []
    
    var columns: [GridItem] = Array(repeating: .init(.fixed(200)), count: 2)
    
    var body: some View {
        ScrollView(.vertical) {
            LazyVGrid(columns: columns, alignment: .center, spacing: 200) {
                ForEach (memos, id: \.objectId) { memo in
                    GridImageView(memo: memo)
                }
            }
            .onAppear() {
                getAllPhotos()
            }
        }
    }
    func getAllPhotos() {
      // 写真をファイルストアから取得(後述)
    }
}

ModalView

写真をタップした際にモーダル表示します。

struct ModalView: View {
    @Binding var isActive: Bool
    @State var memo: NCMBObject
    @State var imageData: Data
    
    var body: some View {
        HStack {
            Spacer()
            VStack {
                Spacer()
                GeometryReader { geometry in
                    Image(uiImage: UIImage(data: imageData)!)
                        .resizable()
                        .scaledToFill()
                        .frame(width: geometry.size.width, height: geometry.size.height)
                        .clipped()
                }
                if let text: String = memo["text"] {
                    Text(text).padding()
                }
                Button("閉じる") {
                    isActive = false
                }
                Spacer()
            }
            Spacer()
        }
        .padding()
        .background(Color(.black))
    }
}

GridImageView

写真一覧のグリッド表示用です。

struct GridImageView: View {
    @State private var fileName: String = ""
    @State private var imageData: Data? = .init(capacity:0)
    @State var memo: NCMBObject? = nil
    @State private var isShowing = false
    
    var body: some View {
        GeometryReader { geometry in
            if imageData?.count ?? 0 > 0 {
                Image(uiImage: UIImage(data: imageData!)!)
                    .resizable()
                    .scaledToFill()
                    .frame(width: geometry.size.width, height: geometry.size.width)
                    .clipped()
            } else {
                Rectangle().fill(Color.clear)
            }
        }.onAppear() {
            loadImage()
        }.onTapGesture {
            isShowing = true
        }.fullScreenCover(isPresented: $isShowing) {
            ModalView(isActive: $isShowing, memo: memo!, imageData: imageData!)
        }
        
    }

    func loadImage() {
      // 写真データを読み込みます(後述)
    }
}

Swift SDKの導入法

Swift SDKはCocoaPods向けに提供しています。Xcodeで新しいiOSプロジェクトを作ったら、Podfileを用意します。

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

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

  # Pods for todoapp
  pod 'NCMB', :git => 'https://github.com/NIFCLOUD-mbaas/ncmb_swift.git'
end

そして pod install でSwift SDKをインストールします。

初期化について

現在、新規でiOSアプリを作成すると、InterfaceがSwiftUI、Life CycleがSwiftUI Appとなっています。この状態で作ると AppDelegate.swift はなく、 (アプリ名)App.swift というファイルが作られます。

この場合、まずSwift SDKを読み込みます。

import SwiftUI
import NCMB   // 追加

そして var body の上に @Environment を追加します。

@Environment(\.scenePhase) private var scenePhase // 追加
var body: some Scene {

最後に WindowGrouponChange を追加します。このonChangeの中で初期化処理を行います。

WindowGroup {
    ContentView()
}
.onChange(of: scenePhase) { scene in
    switch scene {
    case .active:
        NCMB.initialize(applicationKey: "YOUR_APPLICATION_KEY", clientKey: "YOUR_CLIENT_KEY")
    case .background:
        print("background")
    case .inactive:
        print("inactive")
    @unknown default:
        print("default")
    }
}

これで利用可能になります。

まとめ

今回は必要な画面の内容と、NCMBのSwift SDK導入までを紹介しました。次回は認証処理と写真アップロードを解説します。

イントロダクション (Swift) : クイックスタート | ニフクラ mobile backend

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?