20191101のSwiftに関する記事は8件です。

iPadOSでの現在表示しているSceneの取得方法

iOS,Android開発初心者です.Qiita投稿もほぼ初心者なので間違っていたら色々指摘していただけると幸いです.

iPadOS13よりUIScene APIが導入されました.UIApplicationでは1つのウィンドウのみを考えて開発してたのに対し,UISceneにより複数ウィンドウを管理できるようになりました.そこでSceneごとに変数を設定したいと思ったら意外にもハマってしまったのでここで共有します.

今までの異なるView Controller間の変数の共有方法

いくつか方法があるそうですが,初心者向けのサイトなどで紹介されている方法としてはAppDelegate.swiftに変数を宣言.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    var hogehoge: String = "hogehoge"

そして使いたいView Controllerで以下のように取得できる.

let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
let hogehoge = appDelegate.hogehoge

しかしながらこの方法ではすべてのマルチウィンドウで同じ変数を共有してしまう.
またこの方法以外にもシングルトンを使う方法などがあるがシングルトンもマルチウィンドウで同じ変数を共有してしまう.そのためシングルトンなどを今まで使っていた人はUISceneSession#persistentIdentifierで対応する必要がある.(https://qiita.com/snoozelag/items/fe95df7e748ad5fc3205 参照)

マルチウィンドウにおける異なるView Controller間同士の変数の共有方法

まずいままでと同じようにSceneDelegate.swiftに変数を宣言.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var hogehoge: string = "hogehoge"

そして使いたいView Controllerで以下のように取得可能.

let scene:UIScene! = UIApplication.shared.connectedScenes.filter{
      $0.session.configuration.storyboard == self.storyboard
}.first
let sceneDelegate:SceneDelegate! = scene.delegate as? SceneDelegate
let hogehoge = sceneDelegate.hogehoge

UIApplication.shared.connectedScenesはマルチウィンドウに表示しているすべてのSceneを取得してしまう.そこで現在表示しているstoryboardが$0.session.configuration.storyboardと等しいことを確認することで取得可能になる.
他にいい方法とかあれば教えてほしい....

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode11.1, Swift】Deployment Target iOS13.1でも、実機iOS12.4でビルドする方法(図解)

スクリーンショット 2019-10-31 17.26.13のコピー.png
A build only device cannot be used to run this target.
Please select only device or choose a simulated devices as the destination.

ビルド専用デバイスを使用してこのターゲットを実行することはできません。
デバイスのみを選択するか、シミュレートされたデバイスを宛先として選択してください。

はじめに

全地球6億人のロボカッケー皆さんコンバトラー†[□ □]■━
私は王国騎士ロボットVTuberのKoshiだ??

iOS13.1(2019.10.31時点)、最新バージョンじゃないとビルドできないよと
表示された。
何が何でも実機iOS12.4でビルドしたかったので、試行錯誤して実機にビルドできたので、その方法を示す。

動作環境

macOS Mojave
Xcode Version 11.1 (11A1027)
iPhone XS MAX

対処手順

1. iOS13.1のプロジェクトをバックアップ用に複製する

2. iOS12.4のプロジェクトを新規作成する

Targets > General > Deployment Info Target
ビルドしたいデバイス(ターゲット)のOSバージョンに合わせてビルドする。
スクリーンショット 2019-10-31 18.00.51のコピー.png

Project > info > Deployment Info Target >
iOS Deployment Target 12.4をクリック
スクリーンショット 2019-10-31 18.01.11のコピー.png

3. ビルドを実行すると、バージョンに対応していないため、エラーが起こります。

スクリーンショット 2019-10-31 17.44.13のコピー.png

エラーが起こったファイルは、SenceDelegate.swiftAppleDelegate.swift

4. SenceDelegate.swiftAppleDelegate.swiftを全てFixする

スクリーンショット 2019-10-31 18.02.53.png

最後まで続けて、修正できない箇所があれば、コメントアウトします。
今回は、AppleDelegate.swiftにあった。

スクリーンショット 2019-10-31 18.16.11.png

スクリーンショット 2019-10-31 18.16.40.png

全部Fix出来たら、ビルドします。
上手くビルド出来たら成功です。

もし、実機でビルドが上手くいかない場合

スクリーンショット 2019-10-31 18.25.21のコピー.png

ログに
[Application]The app delegate must implement the window property if it wants to use a main storyboard file.

Main.storyboardファイルを使用する場合、アプリデリゲートはウィンドウプロパティを実装する必要があります。

と出た場合、AppleDelegate.swiftに次のコードを記述します。

var window: UIWindow?

スクリーンショット 2019-11-01 21.10.59.png

これでもう一度を試します。

これでビルドに成功しました。

†■━ †■━ †■━ †■━ †■━ †■━ †■━
もし、この記事が役に立ったら、
いいね」「ストック
押してくれ??

以上、Unity王国騎士ロボットVTuber Koshiがお送りしました!

私のステータスだ⚔共にこの世界を救おう
Twitter⚔ twitter.com/Koshi_Vtuber
YouTube⚔ t.co/I9eMMgpS8P?amp=1
niconico⚔ nicovideo.jp/user/90553212
bilibili⚔ space.bilibili.com/476988586
公式HP⚔ koshi-4092b.firebaseapp.com

【参考】
Xcode11 で iOS 12 以前をターゲッドにしてビルドする方法
https://swift-ios.keicode.com/devenv/xcode-how-to-build-for-12-with-xcode11.php

[Xcode]iPhoneの実機テストエラー・ビルドエラーの一覧と対処方法
https://ticklecode.com/xcodeerror/#The_run_destination_XXXXX_iPhone_is_not_valid_for_Running_the_scheme_MyJanken

the run destination is not valid for running the scheme【Xcode、iOS】
https://apprili.com/2018/09/05/the-run-destination-is-not-valid-for-running-the-scheme%E3%80%90xcode%E3%80%81ios%E3%80%91/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた。

SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた

はじめに

今回はFirebaseがとても使いやすいと聞いたので試しに簡単にアプリを作ってみました。

備忘録作成アプリというのは「このエラー前も出たけどなんだっけ」だったり「このライブラリのこの関数どうやって使うんだっけ」と言ったときもう一度調べ直すのは面倒臭いので、それを備忘録として作成できるアプリです。この記事ではどのように開発していったか説明していきます。まだ初心者ですので指摘等あったらお願いします。PythonもやっているのでPythonの備忘録を例としてやっていきます。

ユーザー管理

ログイン画面

スクリーンショット 2019-11-01 20.14.06.png

@IBAction func logInButtonPushed(_ sender: Any) {
        let mail = mailField.text!
        let password = passwordField.text!
        Auth.auth().signIn(withEmail: mail, password: password){ (result, error) in if error == nil, let result = result, result.user.isEmailVerified{
            self.performSegue(withIdentifier: "toMainView", sender: result.user)
        }else if error != nil{
            let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
            }}
    }

これはログインボタンを押したときの処理です。実際にログインするところはAuth.auth().signIn(withEmail: mail, password: password)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてresult.user.isEmailVerifiedでユーザーのメール認証が終わっているかを確認します。

else if error != nilでパスワードとメールアドレスが正しいかを確認し、正しくない場合はアラートを出すようにしています。

登録画面

スクリーンショット 2019-11-01 20.14.52.png

@IBAction func registerButtonPushed(_ sender: Any) {
        let userMail = mailField.text!
        let userPassword = passwordField.text!
        if userPassword.count < 6{
            let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
        auth.createUser(withEmail: userMail, password: userPassword){ (result, error) in
        if error == nil, let result = result {
            result.user.sendEmailVerification(completion: { (error) in if error == nil{
                let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                }})
        }else if error != nil{
            let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
}

これは登録ボタンを押したときの処理です。Firebaseはパスワードが6文字以上でないとエラーになってしまうのでif userPassword.count < 6とすることでユーザーに何がエラーかをわかるようにしています。

ユーザーを作成するところはauth.createUser(withEmail: userMail, password: userPassword)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてメールを送る処理はresult.user.sendEmailVerificationになっています。

エアーが出た場合はアラートを出すことでユーザーに知らせます。

ログインしているか確認

これは起動したとき最初に出てくる画面です。

@IBAction func startButtonPushed(_ sender: Any) {
        if auth.currentUser != nil{
        auth.currentUser?.reload(completion: { error in if error == nil{
            if self.auth.currentUser?.isEmailVerified == true{
                self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
            }else if self.auth.currentUser?.isEmailVerified == false{
                let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }})

        }else if auth.currentUser == nil{
            self.performSegue(withIdentifier: "toLogInView", sender: nil)
        }
    }

auth.currentUser != nilで端末でユーザーが存在しているかをチェックしていて存在していなければ、ログイン画面に、ログインしていたら普通の画面に飛ばします。また、存在してもメール認証が完了していない場合があるので、self.auth.currentUser?.isEmailVerified == falseとすることでメール認証が完了していなかったときは、アラートを出して、メールを確認するように促します。

ログアウト

@IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                try? Auth.auth().signOut()
                let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
                self.navigationController?.pushViewController(StartViewController, animated: true)
            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)

        }

try? Auth.auth().signOut()で実際にログアウトします。ログインする前にアラートで確認します。このボタンは登録画面とログイン画面とスタート画面以外に全て配置しました。

alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in

この部分でアラートのOKボタンが押されたときの処理を書きます。そしてログアウトし終わったときself.navigationController?.pushViewController(StartViewController, animated: true)でスタート画面に戻るようにします。これをしないとそのまま操作ができてしまうからです。

備忘録画面

スクリーンショット 2019-11-01 10.54.57.png スクリーンショット 2019-11-01 11.03.21.png

左が備忘録一覧で、右が備忘録追加画面になっています。

データの読み込み

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
            for document in snapshot.documents{
                let data = document.data()
                let dictionaryName = data["dictionaryName"]
                let dictionaryWord = data["dictionaryWord"]
                self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
                let keys = Array(self.dictionary.keys)
                self.dictionaries = []
                for key in keys{
                    self.dictionaries.append(key)
                }
            }
            self.dictionaryList.reloadData()
            }}
    }

データベースから備忘録を読み込む処理です。コレクションの名前をユーザーIDにすることでそれぞれのユーザの備忘録を表示できます。

ここで忘れてはいけないのが、viewWillAppearよりもTableViewの読み込みが行われてしまうので、dictionariesを定義するときvar dictionaries: Array<String>!としまうと最初に読み込む時にcellの数を渡すときにdictionaries.countのところでnilとなってしまいエラーが起きてしまいます。それを防ぐためには、var dictionaries: Array<String> = []と最初に空のリストを代入し、nilが出ないようにします。そしてデータベースから取得した後dictionaryList.reloadData()とTableViewを更新することで最初に起動したときにDatabaseの中身を表示できます。

検索機能

次に検索機能について説明します。

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.view.endEditing(true)
        searchBar.showsCancelButton = true
        self.searchResult = dictionaries.filter{
            $0.lowercased().contains(searchBar.text!.lowercased())
        }

        self.dictionaryList.reloadData()
    }
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = false
        self.view.endEditing(true)
        searchBar.text = ""
        self.dictionaryList.reloadData()
    }
    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        searchBar.showsCancelButton = true
        return true
    }

$0.lowercased().contains(searchBar.text!.lowercased()) でリストの中から大文字小文字どちらも検索できます。検索したものをsearchResultに追加します。
searchBarCancelButtonClickedでキャンセルボタンの処理を、searchBarShouldBeginEditingでキャンセルボタンを表示させる処理を書きます。

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if dictionarySearchBar.text! == ""{
            return dictionaries.count
        }
        else{
            return searchResult.count
        }
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
        if dictionarySearchBar.text! == ""{
            cell.dictionaryName.text = self.dictionaries[indexPath.row]
        }else{
            cell.dictionaryName.text = self.searchResult[indexPath.row]
        }
        return cell
    }

重要なのがこの部分です。検索しない時はdatabaseから取得したdictionariesをTableViewに表示させればいいのですが、検索したとき表示させたいものはsearchResultというリストなのでdictionarySearchBar.text! == ""で検索欄の中身に何もテキストがない時はdictionariesを表示し検索欄にテキストが入っている時はsearchResultの方を表示させます。

削除機能

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete{
            let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                if self.dictionarySearchBar.text ==  ""{
                    self.dictionaries.remove(at: indexPath.row)
                    let keys = Array(self.dictionary.keys)
                    self.dictionary[keys[indexPath.row]] = nil
                        self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
                }else{
                    if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
                        self.dictionaries.remove(at: index)
                    }
                    self.dictionary[self.searchResult[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
                    self.searchResult.remove(at: indexPath.row)
                }
                tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }

削除するときにも検索欄にテキストが入っているかによって処理を分けます。

検索欄にテキストがない場合は、

1:self.dictionaries.remove(at: indexPath.row)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

2:self.dictionary[keys[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

3:self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()でDatabase内のデータを削除

4:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除

検索欄にテキストがある時は、

1:self.searchResult[indexPath.row])で検索結果のリストから削除

2:self.dictionaries.remove(at: index)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

3:self.dictionary[self.searchResult[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

4:self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()でDatabase内のデータを削除

5:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除

備忘録追加画面

@IBAction func addDictionary(_ sender: Any) {
        let dictionaryName = dictionaryNameField.text!
        dictionaryNameField.text! = ""
        database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
            self.allData = []
            print(snapshot.documents)
            for documet in snapshot.documents{
                let data = documet.data()
                self.allData.append(data["dictionaryName"] as! String)
            }
            if self.allData.contains(dictionaryName) || dictionaryName == ""{            let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
                saveDocument.setData([
                    "dictionaryName": dictionaryName,
                    "dictionaryWord": [:] as! Dictionary<String, String>
                ]){ error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}
            }
            }

        }
}

ここで重要なのが同じ名前がすでに存在しているときや空欄の時は追加できないようにすることです。

self.allData.contains(dictionaryName) || dictionaryName == ""allDataというすべての備忘録の名前が入っているリストの中にtextFieldから取得したdictionaryNameが入っている時と何もない時にアラートを出すようにしています。

言葉追加画面

ここでは備忘録内に実際に言葉を追加する画面です。

ここの処理は備忘録を追加する処理と似ているので最後にコードを載せるだけにしておきます。

スクリーンショット 2019-11-01 18.29.35.pngスクリーンショット 2019-11-01 18.29.22.png

デモ

画面収録-2019-11-01-18.35.20.gif
画面収録-2019-11-01-18.33.09.gif

参考

FirebaseでiOS版簡易SNSを作成する。(メール認証編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 前編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 後編)

@fummicc1-subさんの記事はとても参考になりました。とても感謝してます!

これから

今考えているのは備忘録を他の人に共有できるような機能もつけたいと考えています。

最後まで読んでいただきありがとうございました!

指摘、修正等あったらコメントでお願いします!

コード

StartViewController.swift(最初の画面)
import UIKit
import Firebase

class StartViewController: UIViewController {

    var auth: Auth!

    override func viewDidLoad() {
        super.viewDidLoad()
        auth = Auth.auth()
    }
    @IBAction func startButtonPushed(_ sender: Any) {
        if auth.currentUser != nil{
        auth.currentUser?.reload(completion: { error in if error == nil{
            if self.auth.currentUser?.isEmailVerified == true{
                self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
            }else if self.auth.currentUser?.isEmailVerified == false{
                let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }})

        }else if auth.currentUser == nil{
            self.performSegue(withIdentifier: "toLogInView", sender: nil)
        }
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toMainView"{
            let user = sender as! User
            let MainViewController = segue.destination as! ViewController
            MainViewController.me = AppUser(data: ["userId": user.uid])
        }
    }

}

LogInViewController.swift(登録画面)
import UIKit
import Firebase

class LogInViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var mailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    @IBOutlet weak var logInButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        mailField.delegate = self
        passwordField.delegate = self
    }
    @IBAction func logInButtonPushed(_ sender: Any) {
        let mail = mailField.text!
        let password = passwordField.text!
        Auth.auth().signIn(withEmail: mail, password: password){ (result, error) in if error == nil, let result = result, result.user.isEmailVerified{
            self.performSegue(withIdentifier: "toMainView", sender: result.user)
        }else if error != nil{
            let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
            }}
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier  == "toMainView"{
            let user = sender as! User
            let MainViewController = segue.destination as! ViewController
            MainViewController.me = AppUser(data: ["userId": user.uid])

        }
    }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

RegisterViewController.swift(登録画面)
import UIKit
import Firebase
import FirebaseAuth

class RegisterViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var mailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    @IBOutlet weak var registerButton: UIButton!
    var auth: Auth!
    var me: AppUser!

    override func viewDidLoad() {
        super.viewDidLoad()
        auth = Auth.auth()
        mailField.delegate = self
        passwordField.delegate = self
    }


    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func registerButtonPushed(_ sender: Any) {
        let userMail = mailField.text!
        let userPassword = passwordField.text!
        if userPassword.count < 6{
            let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
        auth.createUser(withEmail: userMail, password: userPassword){ (result, error) in
        if error == nil, let result = result {
            result.user.sendEmailVerification(completion: { (error) in if error == nil{
                let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                }})
        }else if error != nil{
            let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
}
}


ViewController.swift(備忘録一覧画面)
import UIKit
import GoogleSignIn
import Firebase
import FirebaseFirestore

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UINavigationControllerDelegate, UISearchBarDelegate {

    var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
    @IBOutlet weak var dictionaryList: UITableView!
    var me: AppUser!
    var database: Firestore!
    var searchResult: [String]!
    var dictionaries: Array<String> = []
    let dictionarySearchBar: UISearchBar = UISearchBar()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationItem.hidesBackButton = true
        database = Firestore.firestore()
        dictionarySearchBar.delegate = self
        dictionaryList.delegate = self
        dictionaryList.dataSource = self
        dictionaryList.register(UINib(nibName: "DictionaryTableViewCell", bundle: nil), forCellReuseIdentifier: "DictionaryTableViewCell")


        // Do any additional setup after loading the view.
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
            for document in snapshot.documents{
                let data = document.data()
                let dictionaryName = data["dictionaryName"]
                let dictionaryWord = data["dictionaryWord"]
                self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
                let keys = Array(self.dictionary.keys)
                self.dictionaries = []
                for key in keys{
                    self.dictionaries.append(key)
                }
            }
            self.dictionaryList.reloadData()
            }}

    }
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.view.endEditing(true)
        searchBar.showsCancelButton = true
        self.searchResult = dictionaries.filter{
            $0.lowercased().contains(searchBar.text!.lowercased())
        }

        self.dictionaryList.reloadData()
    }
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = false
        self.view.endEditing(true)
        searchBar.text = ""
        self.dictionaryList.reloadData()
    }
    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        searchBar.showsCancelButton = true
        return true
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if dictionarySearchBar.text! == ""{
            return dictionaries.count
        }
        else{
            return searchResult.count
        }
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
        if dictionarySearchBar.text! == ""{
            cell.dictionaryName.text = self.dictionaries[indexPath.row]
        }else{
            cell.dictionaryName.text = self.searchResult[indexPath.row]
        }
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        dictionarySearchBar.placeholder = "辞書を検索"
        return dictionarySearchBar
    }
    @IBAction func goToAddView(_ sender: Any) {
        performSegue(withIdentifier: "toAddView", sender: dictionary)
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toAddView"{
            let AddViewController = segue.destination as! AddDictionaryViewController
            AddViewController.me = me
        }else if segue.identifier == "toDictionaryView"{
            let DictionaryViewController = segue.destination as! DictionaryViewController
            DictionaryViewController.dictionaryName = (sender as! String)
            DictionaryViewController.me = me

        }
    }
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete{
            let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                if self.dictionarySearchBar.text ==  ""{
                    self.dictionaries.remove(at: indexPath.row)
                    let keys = Array(self.dictionary.keys)
                    self.dictionary[keys[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
                }else{
                    if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
                        self.dictionaries.remove(at: index)
                    }
                    self.dictionary[self.searchResult[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
                    self.searchResult.remove(at: indexPath.row)
                }
                tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)

    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let keys = Array(self.dictionary.keys)
        if dictionarySearchBar.text == ""{
            performSegue(withIdentifier: "toDictionaryView", sender: keys[indexPath.row])
        }else{
            performSegue(withIdentifier: "toDictionaryView", sender: searchResult[indexPath.row])
        }

    }

}

AddDictionaryViewController.swift(備忘録追加画面)
import UIKit
import Firebase
import FirebaseFirestore

class AddDictionaryViewController: UIViewController, UITextFieldDelegate {

    var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
    var database: Firestore!
    var me: AppUser!

    @IBOutlet weak var dictionaryNameField: UITextField!
    @IBOutlet weak var dictionaryLabel: UILabel!
    @IBOutlet weak var addButton: UIButton!
    var allData: Array<String> = []

    override func viewDidLoad() {
        super.viewDidLoad()
        database = Firestore.firestore()
        dictionaryNameField.delegate = self
    }


    @IBAction func addDictionary(_ sender: Any) {
        let dictionaryName = dictionaryNameField.text!
        dictionaryNameField.text! = ""
        database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
            self.allData = []
            print(snapshot.documents)
            for documet in snapshot.documents{
                let data = documet.data()
                self.allData.append(data["dictionaryName"] as! String)
            }
            if self.allData.contains(dictionaryName) || dictionaryName == ""{            let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
                saveDocument.setData([
                    "dictionaryName": dictionaryName,
                    "dictionaryWord": [:] as! Dictionary<String, String>
                ]){ error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}
            }
            }

        }

        }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                try? Auth.auth().signOut()
                let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
                self.navigationController?.pushViewController(StartViewController, animated: true)
            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)

        }
}

AddDictioaryWordViewController.swift(備忘録内言葉追加画面)
import UIKit
import Firebase

class AddDictionaryWordViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var wordField: UITextField!
    @IBOutlet weak var explanationView: UITextView!
    var dictionaryName: String!
    var me: AppUser!
    var database: Firestore!
    var dictionaryWords: Dictionary<String, String>!
    var allData: Array<String>!
    override func viewDidLoad() {
        super.viewDidLoad()
        database = Firestore.firestore()
        wordField.delegate = self
        explanationView.layer.cornerRadius = 5
        explanationView.layer.masksToBounds = true
        setupTextView()
    }
    @IBAction func addButtonPushed(_ sender: Any) {
        let wordName = wordField.text!
        let wordExplanation = explanationView.text!
        database.collection(me.userId).document(dictionaryName).getDocument{ (document, error) in if error == nil, let document = document{
            let data = document.data()!
            let words = data["dictionaryWord"] as! Dictionary<String, String>
            self.allData = Array(words.keys)
            }
            if self.allData.contains(wordName) || wordName == ""{
                let alert = UIAlertController(title: "同じ名前の辞書は追加できません。", message: "その名前の辞書は既に存在しています。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(self.dictionaryName)
                self.dictionaryWords.updateValue(wordExplanation, forKey: wordName)
                saveDocument.setData([
                    "dictionaryName": self.dictionaryName!,
                    "dictionaryWord": self.dictionaryWords!
                ]){error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}

            }
        }


    }
    func setupTextView() {
        let toolBar = UIToolbar()
        let flexibleSpaceBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
        toolBar.items = [flexibleSpaceBarButton, doneButton]
        toolBar.sizeToFit()
        explanationView.inputAccessoryView = toolBar
    }
    @objc func dismissKeyboard() {
        explanationView.resignFirstResponder()
    }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}

DictionaryWordViewController.swift(備忘録内言葉画面)
import UIKit
import Firebase

class DictionaryWordViewController: UIViewController {

    @IBOutlet weak var wordNameLabel: UILabel!
    @IBOutlet weak var wordExplanationView: UITextView!
    var wordName = ""
    var wordExplanation = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        wordNameLabel.text! = wordName
        wordExplanationView.text! = wordExplanation

    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }

}

User.swift(ユーザーのモデル作成)
import Foundation

struct AppUser {
    let userId: String

    init(data: [String: Any]) {
        userId = data["userId"] as! String
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた

はじめに

今回はFirebaseがとても使いやすいと聞いたので試しに簡単にアプリを作ってみました。

備忘録作成アプリというのは「このエラー前も出たけどなんだっけ」だったり「このライブラリのこの関数どうやって使うんだっけ」と言ったときもう一度調べ直すのは面倒臭いので、それを備忘録として作成できるアプリです。この記事ではどのように開発していったか説明していきます。まだ初心者ですので指摘等あったらお願いします。PythonもやっているのでPythonの備忘録を例としてやっていきます。

ユーザー管理

ログイン画面

スクリーンショット 2019-11-01 20.14.06.png

@IBAction func logInButtonPushed(_ sender: Any) {
    let mail = mailField.text!
    let password = passwordField.text!
    Auth.auth().signIn(withEmail: mail, password: password) { (result, error) in
        if error == nil, let result = result, result.user.isEmailVerified {
            self.performSegue(withIdentifier: "toMainView", sender: result.user)
        } else if error != nil {
            let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
}

これはログインボタンを押したときの処理です。実際にログインするところはAuth.auth().signIn(withEmail: mail, password: password)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてresult.user.isEmailVerifiedでユーザーのメール認証が終わっているかを確認します。

else if error != nilでパスワードとメールアドレスが正しいかを確認し、正しくない場合はアラートを出すようにしています。

登録画面

スクリーンショット 2019-11-01 20.14.52.png

@IBAction func registerButtonPushed(_ sender: Any) {
    let userMail = mailField.text!
    let userPassword = passwordField.text!
    if userPassword.count < 6 {
        let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
    auth.createUser(withEmail: userMail, password: userPassword) { (result, error) in
        if error == nil, let result = result {
            result.user.sendEmailVerification { (error) in 
                if error == nil {
                    let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
                    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                    self.present(alert, animated: true, completion: nil)
                }
            }
        } else if error != nil {
            let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
}

これは登録ボタンを押したときの処理です。Firebaseはパスワードが6文字以上でないとエラーになってしまうのでif userPassword.count < 6とすることでユーザーに何がエラーかをわかるようにしています。

ユーザーを作成するところはauth.createUser(withEmail: userMail, password: userPassword)withEmail:にtextFieldから受け取ったメールアドレスを、password:にtextFieldのpasswordを入れます。

そしてメールを送る処理はresult.user.sendEmailVerificationになっています。

エアーが出た場合はアラートを出すことでユーザーに知らせます。

ログインしているか確認

これは起動したとき最初に出てくる画面です。

@IBAction func startButtonPushed(_ sender: Any) {
        if auth.currentUser != nil{
        auth.currentUser?.reload(completion: { error in if error == nil{
            if self.auth.currentUser?.isEmailVerified == true{
                self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
            }else if self.auth.currentUser?.isEmailVerified == false{
                let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }})

        }else if auth.currentUser == nil{
            self.performSegue(withIdentifier: "toLogInView", sender: nil)
        }
    }

auth.currentUser != nilで端末でユーザーが存在しているかをチェックしていて存在していなければ、ログイン画面に、ログインしていたら普通の画面に飛ばします。また、存在してもメール認証が完了していない場合があるので、self.auth.currentUser?.isEmailVerified == falseとすることでメール認証が完了していなかったときは、アラートを出して、メールを確認するように促します。

ログアウト

@IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                try? Auth.auth().signOut()
                let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
                self.navigationController?.pushViewController(StartViewController, animated: true)
            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)

        }

try? Auth.auth().signOut()で実際にログアウトします。ログインする前にアラートで確認します。このボタンは登録画面とログイン画面とスタート画面以外に全て配置しました。

alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in

この部分でアラートのOKボタンが押されたときの処理を書きます。そしてログアウトし終わったときself.navigationController?.pushViewController(StartViewController, animated: true)でスタート画面に戻るようにします。これをしないとそのまま操作ができてしまうからです。

備忘録画面

スクリーンショット 2019-11-01 10.54.57.png スクリーンショット 2019-11-01 11.03.21.png

左が備忘録一覧で、右が備忘録追加画面になっています。

データの読み込み

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
            for document in snapshot.documents{
                let data = document.data()
                let dictionaryName = data["dictionaryName"]
                let dictionaryWord = data["dictionaryWord"]
                self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
                let keys = Array(self.dictionary.keys)
                self.dictionaries = []
                for key in keys{
                    self.dictionaries.append(key)
                }
            }
            self.dictionaryList.reloadData()
            }}
    }

データベースから備忘録を読み込む処理です。コレクションの名前をユーザーIDにすることでそれぞれのユーザの備忘録を表示できます。

ここで忘れてはいけないのが、viewWillAppearよりもTableViewの読み込みが行われてしまうので、dictionariesを定義するときvar dictionaries: Array<String>!としまうと最初に読み込む時にcellの数を渡すときにdictionaries.countのところでnilとなってしまいエラーが起きてしまいます。それを防ぐためには、var dictionaries: Array<String> = []と最初に空のリストを代入し、nilが出ないようにします。そしてデータベースから取得した後dictionaryList.reloadData()とTableViewを更新することで最初に起動したときにDatabaseの中身を表示できます。

検索機能

次に検索機能について説明します。

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.view.endEditing(true)
        searchBar.showsCancelButton = true
        self.searchResult = dictionaries.filter{
            $0.lowercased().contains(searchBar.text!.lowercased())
        }

        self.dictionaryList.reloadData()
    }
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = false
        self.view.endEditing(true)
        searchBar.text = ""
        self.dictionaryList.reloadData()
    }
    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        searchBar.showsCancelButton = true
        return true
    }

$0.lowercased().contains(searchBar.text!.lowercased()) でリストの中から大文字小文字どちらも検索できます。検索したものをsearchResultに追加します。
searchBarCancelButtonClickedでキャンセルボタンの処理を、searchBarShouldBeginEditingでキャンセルボタンを表示させる処理を書きます。

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if dictionarySearchBar.text! == ""{
            return dictionaries.count
        }
        else{
            return searchResult.count
        }
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
        if dictionarySearchBar.text! == ""{
            cell.dictionaryName.text = self.dictionaries[indexPath.row]
        }else{
            cell.dictionaryName.text = self.searchResult[indexPath.row]
        }
        return cell
    }

重要なのがこの部分です。検索しない時はdatabaseから取得したdictionariesをTableViewに表示させればいいのですが、検索したとき表示させたいものはsearchResultというリストなのでdictionarySearchBar.text! == ""で検索欄の中身に何もテキストがない時はdictionariesを表示し検索欄にテキストが入っている時はsearchResultの方を表示させます。

削除機能

func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete{
            let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                if self.dictionarySearchBar.text ==  ""{
                    self.dictionaries.remove(at: indexPath.row)
                    let keys = Array(self.dictionary.keys)
                    self.dictionary[keys[indexPath.row]] = nil
                        self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
                }else{
                    if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
                        self.dictionaries.remove(at: index)
                    }
                    self.dictionary[self.searchResult[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
                    self.searchResult.remove(at: indexPath.row)
                }
                tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }

削除するときにも検索欄にテキストが入っているかによって処理を分けます。

検索欄にテキストがない場合は、

1:self.dictionaries.remove(at: indexPath.row)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

2:self.dictionary[keys[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

3:self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()でDatabase内のデータを削除

4:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除

検索欄にテキストがある時は、

1:self.searchResult[indexPath.row])で検索結果のリストから削除

2:self.dictionaries.remove(at: index)dictionaryのkeyで備忘録の名前が入っていてTableViewに表示されさせるものを削除

3:self.dictionary[self.searchResult[indexPath.row]] = nilで備忘録名と備忘録内の言葉を辞書型に格納したものを削除(辞書なのでnilを代入することで削除できる)

4:self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()でDatabase内のデータを削除

5:tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)でTableViewのCellを削除

備忘録追加画面

@IBAction func addDictionary(_ sender: Any) {
        let dictionaryName = dictionaryNameField.text!
        dictionaryNameField.text! = ""
        database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
            self.allData = []
            print(snapshot.documents)
            for documet in snapshot.documents{
                let data = documet.data()
                self.allData.append(data["dictionaryName"] as! String)
            }
            if self.allData.contains(dictionaryName) || dictionaryName == ""{            let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
                saveDocument.setData([
                    "dictionaryName": dictionaryName,
                    "dictionaryWord": [:] as! Dictionary<String, String>
                ]){ error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}
            }
            }

        }
}

ここで重要なのが同じ名前がすでに存在しているときや空欄の時は追加できないようにすることです。

self.allData.contains(dictionaryName) || dictionaryName == ""allDataというすべての備忘録の名前が入っているリストの中にtextFieldから取得したdictionaryNameが入っている時と何もない時にアラートを出すようにしています。

言葉追加画面

ここでは備忘録内に実際に言葉を追加する画面です。

ここの処理は備忘録を追加する処理と似ているので最後にコードを載せるだけにしておきます。

スクリーンショット 2019-11-01 18.29.35.pngスクリーンショット 2019-11-01 18.29.22.png

デモ

画面収録-2019-11-01-18.35.20.gif
画面収録-2019-11-01-18.33.09.gif

参考

FirebaseでiOS版簡易SNSを作成する。(メール認証編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 前編)

FirebaseでiOS版簡易SNSを作成する。(タイムライン編 後編)

@fummicc1-subさんの記事はとても参考になりました。とても感謝してます!

これから

今考えているのは備忘録を他の人に共有できるような機能もつけたいと考えています。

最後まで読んでいただきありがとうございました!

指摘、修正等あったらコメントでお願いします!

コード

StartViewController.swift(最初の画面)
import UIKit
import Firebase

class StartViewController: UIViewController {

    var auth: Auth!

    override func viewDidLoad() {
        super.viewDidLoad()
        auth = Auth.auth()
    }
    @IBAction func startButtonPushed(_ sender: Any) {
        if auth.currentUser != nil{
        auth.currentUser?.reload(completion: { error in if error == nil{
            if self.auth.currentUser?.isEmailVerified == true{
                self.performSegue(withIdentifier: "toMainView", sender: self.auth.currentUser!)
            }else if self.auth.currentUser?.isEmailVerified == false{
                let alert = UIAlertController(title: "確認用メールを送信しているので確認をお願いします。", message: "まだメール認証が完了していません。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }
        }})

        }else if auth.currentUser == nil{
            self.performSegue(withIdentifier: "toLogInView", sender: nil)
        }
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toMainView"{
            let user = sender as! User
            let MainViewController = segue.destination as! ViewController
            MainViewController.me = AppUser(data: ["userId": user.uid])
        }
    }

}

LogInViewController.swift(登録画面)
import UIKit
import Firebase

class LogInViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var mailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    @IBOutlet weak var logInButton: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        mailField.delegate = self
        passwordField.delegate = self
    }
    @IBAction func logInButtonPushed(_ sender: Any) {
        let mail = mailField.text!
        let password = passwordField.text!
        Auth.auth().signIn(withEmail: mail, password: password){ (result, error) in if error == nil, let result = result, result.user.isEmailVerified{
            self.performSegue(withIdentifier: "toMainView", sender: result.user)
        }else if error != nil{
            let alert = UIAlertController(title: "ログインエラー", message: "パスワードまたはメールアドレスが違います。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
            }}
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier  == "toMainView"{
            let user = sender as! User
            let MainViewController = segue.destination as! ViewController
            MainViewController.me = AppUser(data: ["userId": user.uid])

        }
    }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

RegisterViewController.swift(登録画面)
import UIKit
import Firebase
import FirebaseAuth

class RegisterViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var mailField: UITextField!
    @IBOutlet weak var passwordField: UITextField!
    @IBOutlet weak var registerButton: UIButton!
    var auth: Auth!
    var me: AppUser!

    override func viewDidLoad() {
        super.viewDidLoad()
        auth = Auth.auth()
        mailField.delegate = self
        passwordField.delegate = self
    }


    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func registerButtonPushed(_ sender: Any) {
        let userMail = mailField.text!
        let userPassword = passwordField.text!
        if userPassword.count < 6{
            let alert = UIAlertController(title: "パスワードエラー", message: "パスワードは6文字上にしてください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
        auth.createUser(withEmail: userMail, password: userPassword){ (result, error) in
        if error == nil, let result = result {
            result.user.sendEmailVerification(completion: { (error) in if error == nil{
                let alert = UIAlertController(title: "仮登録を行いました。", message: "入力したメールアドレス宛に確認メールを送信しました。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
                }})
        }else if error != nil{
            let alert = UIAlertController(title: "登録エラー", message: "新規登録ができませんでした。メールアドレスの形式などを確認してください。", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }
}
}


ViewController.swift(備忘録一覧画面)
import UIKit
import GoogleSignIn
import Firebase
import FirebaseFirestore

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UINavigationControllerDelegate, UISearchBarDelegate {

    var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
    @IBOutlet weak var dictionaryList: UITableView!
    var me: AppUser!
    var database: Firestore!
    var searchResult: [String]!
    var dictionaries: Array<String> = []
    let dictionarySearchBar: UISearchBar = UISearchBar()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.navigationItem.hidesBackButton = true
        database = Firestore.firestore()
        dictionarySearchBar.delegate = self
        dictionaryList.delegate = self
        dictionaryList.dataSource = self
        dictionaryList.register(UINib(nibName: "DictionaryTableViewCell", bundle: nil), forCellReuseIdentifier: "DictionaryTableViewCell")


        // Do any additional setup after loading the view.
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        database.collection(me.userId).getDocuments{ (snapshot, error) in if error == nil, let snapshot = snapshot{
            for document in snapshot.documents{
                let data = document.data()
                let dictionaryName = data["dictionaryName"]
                let dictionaryWord = data["dictionaryWord"]
                self.dictionary.updateValue(dictionaryWord as! Dictionary<String, String>, forKey: dictionaryName as! String)
                let keys = Array(self.dictionary.keys)
                self.dictionaries = []
                for key in keys{
                    self.dictionaries.append(key)
                }
            }
            self.dictionaryList.reloadData()
            }}

    }
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.view.endEditing(true)
        searchBar.showsCancelButton = true
        self.searchResult = dictionaries.filter{
            $0.lowercased().contains(searchBar.text!.lowercased())
        }

        self.dictionaryList.reloadData()
    }
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.showsCancelButton = false
        self.view.endEditing(true)
        searchBar.text = ""
        self.dictionaryList.reloadData()
    }
    func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
        searchBar.showsCancelButton = true
        return true
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if dictionarySearchBar.text! == ""{
            return dictionaries.count
        }
        else{
            return searchResult.count
        }
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DictionaryTableViewCell", for: indexPath) as! DictionaryTableViewCell
        if dictionarySearchBar.text! == ""{
            cell.dictionaryName.text = self.dictionaries[indexPath.row]
        }else{
            cell.dictionaryName.text = self.searchResult[indexPath.row]
        }
        return cell
    }
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 50
    }
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        dictionarySearchBar.placeholder = "辞書を検索"
        return dictionarySearchBar
    }
    @IBAction func goToAddView(_ sender: Any) {
        performSegue(withIdentifier: "toAddView", sender: dictionary)
    }
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toAddView"{
            let AddViewController = segue.destination as! AddDictionaryViewController
            AddViewController.me = me
        }else if segue.identifier == "toDictionaryView"{
            let DictionaryViewController = segue.destination as! DictionaryViewController
            DictionaryViewController.dictionaryName = (sender as! String)
            DictionaryViewController.me = me

        }
    }
    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCell.EditingStyle.delete{
            let alert = UIAlertController(title: "削除", message: "本当に削除しますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                if self.dictionarySearchBar.text ==  ""{
                    self.dictionaries.remove(at: indexPath.row)
                    let keys = Array(self.dictionary.keys)
                    self.dictionary[keys[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(keys[indexPath.row]).delete()
                }else{
                    if let index = self.dictionaries.firstIndex(of: self.searchResult[indexPath.row]){
                        self.dictionaries.remove(at: index)
                    }
                    self.dictionary[self.searchResult[indexPath.row]] = nil
                    self.database.collection(self.me.userId).document(self.searchResult[indexPath.row]).delete()
                    self.searchResult.remove(at: indexPath.row)
                }
                tableView.deleteRows(at: [indexPath as IndexPath], with: UITableView.RowAnimation.automatic)

            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)

    }
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let keys = Array(self.dictionary.keys)
        if dictionarySearchBar.text == ""{
            performSegue(withIdentifier: "toDictionaryView", sender: keys[indexPath.row])
        }else{
            performSegue(withIdentifier: "toDictionaryView", sender: searchResult[indexPath.row])
        }

    }

}

AddDictionaryViewController.swift(備忘録追加画面)
import UIKit
import Firebase
import FirebaseFirestore

class AddDictionaryViewController: UIViewController, UITextFieldDelegate {

    var dictionary: Dictionary<String, Dictionary<String, String>> = [:]
    var database: Firestore!
    var me: AppUser!

    @IBOutlet weak var dictionaryNameField: UITextField!
    @IBOutlet weak var dictionaryLabel: UILabel!
    @IBOutlet weak var addButton: UIButton!
    var allData: Array<String> = []

    override func viewDidLoad() {
        super.viewDidLoad()
        database = Firestore.firestore()
        dictionaryNameField.delegate = self
    }


    @IBAction func addDictionary(_ sender: Any) {
        let dictionaryName = dictionaryNameField.text!
        dictionaryNameField.text! = ""
        database.collection(me.userId).getDocuments{(snapshot, error) in if error == nil, let snapshot = snapshot{
            self.allData = []
            print(snapshot.documents)
            for documet in snapshot.documents{
                let data = documet.data()
                self.allData.append(data["dictionaryName"] as! String)
            }
            if self.allData.contains(dictionaryName) || dictionaryName == ""{            let alert = UIAlertController(title: "追加エラー", message: "その名前の辞書は既に存在しているか、辞書名が空欄です。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(dictionaryName)
                saveDocument.setData([
                    "dictionaryName": dictionaryName,
                    "dictionaryWord": [:] as! Dictionary<String, String>
                ]){ error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}
            }
            }

        }

        }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
                (action: UIAlertAction!) -> Void in
                try? Auth.auth().signOut()
                let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
                self.navigationController?.pushViewController(StartViewController, animated: true)
            }))
            alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
            present(alert, animated: true, completion: nil)

        }
}

AddDictioaryWordViewController.swift(備忘録内言葉追加画面)
import UIKit
import Firebase

class AddDictionaryWordViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var wordField: UITextField!
    @IBOutlet weak var explanationView: UITextView!
    var dictionaryName: String!
    var me: AppUser!
    var database: Firestore!
    var dictionaryWords: Dictionary<String, String>!
    var allData: Array<String>!
    override func viewDidLoad() {
        super.viewDidLoad()
        database = Firestore.firestore()
        wordField.delegate = self
        explanationView.layer.cornerRadius = 5
        explanationView.layer.masksToBounds = true
        setupTextView()
    }
    @IBAction func addButtonPushed(_ sender: Any) {
        let wordName = wordField.text!
        let wordExplanation = explanationView.text!
        database.collection(me.userId).document(dictionaryName).getDocument{ (document, error) in if error == nil, let document = document{
            let data = document.data()!
            let words = data["dictionaryWord"] as! Dictionary<String, String>
            self.allData = Array(words.keys)
            }
            if self.allData.contains(wordName) || wordName == ""{
                let alert = UIAlertController(title: "同じ名前の辞書は追加できません。", message: "その名前の辞書は既に存在しています。", preferredStyle: .alert)
                alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                self.present(alert, animated: true, completion: nil)
            }else{
                let saveDocument = self.database.collection(self.me.userId).document(self.dictionaryName)
                self.dictionaryWords.updateValue(wordExplanation, forKey: wordName)
                saveDocument.setData([
                    "dictionaryName": self.dictionaryName!,
                    "dictionaryWord": self.dictionaryWords!
                ]){error in if error == nil{
                    self.navigationController?.popViewController(animated: true)
                    }}

            }
        }


    }
    func setupTextView() {
        let toolBar = UIToolbar()
        let flexibleSpaceBarButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(dismissKeyboard))
        toolBar.items = [flexibleSpaceBarButton, doneButton]
        toolBar.sizeToFit()
        explanationView.inputAccessoryView = toolBar
    }
    @objc func dismissKeyboard() {
        explanationView.resignFirstResponder()
    }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}

DictionaryWordViewController.swift(備忘録内言葉画面)
import UIKit
import Firebase

class DictionaryWordViewController: UIViewController {

    @IBOutlet weak var wordNameLabel: UILabel!
    @IBOutlet weak var wordExplanationView: UITextView!
    var wordName = ""
    var wordExplanation = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        wordNameLabel.text! = wordName
        wordExplanationView.text! = wordExplanation

    }
    @IBAction func logOut(_ sender: Any) {
        let alert = UIAlertController(title: "ログアウト", message: "本当にログアウトしますか?", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: {
            (action: UIAlertAction!) -> Void in
            try? Auth.auth().signOut()
            let StartViewController: StartViewController = self.storyboard?.instantiateViewController(identifier: "start") as! StartViewController
            self.navigationController?.pushViewController(StartViewController, animated: true)
        }))
        alert.addAction(UIAlertAction(title: "キャンセル", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }

}

User.swift(ユーザーのモデル作成)
import Foundation

struct AppUser {
    let userId: String

    init(data: [String: Any]) {
        userId = data["userId"] as! String
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException'

Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: 'Could not instantiate class named _UITextLayoutView because no class named _UITextLayoutView was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target)'

  • Xcode 11.2の場合のみに起きる
  • Storyboard / xib でUITextViewを配置していると起きる
  • 実機で実行した場合のみに起きる

対策

リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのGit

今年(2019年)の4月に中途入社した、ペーペー社員です。

初めてちゃんとした?プロジェクトに入り、Gitを触る機会が増えた為、アプトプット。

中途とはいえ、業界未経験の私には、Gitがなんなのかさっぱり・・・


今回は初投稿という事で、Markdown記法の勉強も兼ねて、Gitの基本的用語から。

※SourceTree(Version:3.1.1)を使用

ローカルリポジトリ

自分のマシン上にあるリポジトリ

リモートリポジトリ

ネットワーク上にあるリポジトリ

Commit(コミット)

ファイルやデュレクトリの変更をローカルリポジトリ内で変更を行う事
commit.png

Push(プッシュ)

ローカルリポジトリの修正内容をリモートリポジトリに反映させる事
push.png

Pull(プル)

リモートリポジトリの内容をローカルリポジトリに取り込む事
pull.png

Fetch(フェッチ)

リモートリポジトリの最新データを取得する事
※Pullと違ってローカルリポジトリに取り込んだりしない
fetch.png

Stash(スタッシュ)

作業中の変更を一時退避させる事
※差し込み作業が入った時に、現時点ではCommitをしたくない時に使用
stash.png

Cherry-pick(チェリーピック)

特定のCommitした内容だけをピックアップして取り込む事(複数OK)
cherry_pick.png

Conflict(コンフリクト)

同じファイルに同じ箇所の変更が同時に起こる事

// コンフリクトの見方
<<<<<< HEAD
自分の変更内容
=====
コンフリクトが起きているブランチ
master >>>>>>

<<<<<< HEAD
private func hogeMethod() {
   print("hoge")
}
=====
private func fugaMethod() {
   print("fuga")
}
master >>>>>>

どちらが正しいのか修正する必要がある



基本的な用語と意味を理解しておくと、操作しやすくなります。

学んだ事や理解したことがどんどん蓄積されていくので、おいおい投稿していこうと思います!

では、また!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

�初心者が作るSwiftでの高機能電卓の作り方 No�,1 忘備録

はじめに
半年ほどpythonでディープラーニングなどを勉強していたけど
複雑になってきてわからなくなったので、
気分転換にswiftに手を出して練習がてらに高機能電卓を作ってみた。

swift勉強期間 1週間ほど

今回の目的は
1、電卓を作る
2、掛け算と割り算を優先して計算する
3、小数点の計算ができる
4、()内を最優先して計算する
5、DELを作る
6、前回の答えを計算式中に組み込む
7、計算式中の値を変更する

今パッと思いついた物を書いただけなので変更する可能性があります。

現在の進度としては「3」の小数点の計算まで進んでいます。

Main.storyboardはこのようになっています。
スクリーンショット 2019-11-01 10.59.23.png
数値や記号を押すたびにFormula.textに計算式を表示させます

それではコードの説明です。
(最後に全コードを載せておきます)

はじめに変数の宣言から

calculator.swift
import UIKit

class ViewController: UIViewController {
    var str_num:String = "" //計算に使用する変数
    var formula:String = "" //式を表示するだけ
    var nums:[Double] = [] //数値を入れるリスト
    var signals:[String] = [] //四則演算記号を入れるリスト
    var temporarys:[Int] = [] //一時的に記憶しておくリスト
    var count:Int = 0 //リストを消去する際に使用
    var ans:String = "" //答えを代入、前回の答えを使用できる
    var signal_on_off = false //on == true , off == false 記号のダブりを防ぐ

変数 temporarys と ans , signal_on_off はなぜ作ったかは、のちにわかります。

nums:[Double]は小数点の計算をするために[Int]ではなく、[Double]をつかいました。

また今回電卓を作るにあたって僕が考えたアルゴリズムは
Ans = 1+2+3*4 を計算する時
nums = [1,2,3,4]
signals = [+,+,*]
をリストに追加して
signals[i]の時nums[i]とnums[i + 1]を計算するという方法です。
これが最適解とはわかりませんが僕はこれで行こうと思います。

calculator.swift
 @IBAction func one(_ sender: Any) {
      calc(num: "1")
  }
  @IBAction func zero(_ sender: Any)
    if str_num == "" || str_num == "0"{
       }else{
       calc(num: "0")
       }
  }

ボタンについて1から9はcalc関数に引数を入れていますが
0だけは厄介で、例えば 000+002+200 は間違いで
0+2+200 にしなければいけないので
calc関数をみるとわかりますがstr_num が""の時と"0"の時は何も実行しないようにします。

calculator.swift
  @IBAction func plus(_ sender: Any) {
        sig(signal:"+")

四則演算記号はsig関数に引数を渡しています。
plas,minus,times,divideは上と引数以外 同じです。

calculator.swift
  func calc(num:String){
        formula += num
        str_num += num
        Formula.text = formula
        signal_on_off = false
    }
    func sig(signal:String){
        if signal_on_off == false{
            formula += signal
            nums.append(Double(str_num)!)
            str_num = ""
            signals.append(signal)
            Formula.text = formula
        }
        signal_on_off = true
    }

まずcalc関数のformulaから 文字列の足し算なのでACを押されるまではボタンが押されるたびに足し合わせることで計算式を表現できる。

次にstr_numについてformulaと同じように見えるが四則演算記号を押されると
変数 str_numは リストnums に追加され str_num = "" とされる。
この操作は sig関数で実行される。

Formula.textで計算式を表示

signals_on_offは例えば、 1++2*-3+*/-4 = ????
など記号が重複することを防ぐため、記号が押されるとtrueが代入され記号の入力を受け付けない
が数値が押されるとfalseが代入され記号の入力ができるようになる。

次にsig関数について
基本先ほどにも書いた通りだが

nums.append(Double(str_num)!)は
str_numをStringからDoubleへ変更させます。この時nilは存在しないので「!」を最後につけます。

calculator.swift
  @IBAction func AC(_ sender: Any) {
        str_num = ""
        formula = ""
        nums.removeAll()
        signals.removeAll()
        temporarys.removeAll()
        count = 0
        Formula.text = "0"
        Ans.text = "0"
    }
  @IBAction func decimal_point(_ sender: Any) {
        if str_num.contains(".") == true{
        }else if str_num == ""{
            calc(num:"0.")
        }else{
            calc(num:".")
        }
    }

次にACと小数点について
ACは見ての通りAllClearします

小数点では例えば 0.00.21.3 + 1.0.2 など どこが小数点かわからなくなるのを防ぐために
str_numのなかに1つ「.」が存在すれば(true)何も実行しない。

また 1 + .01 など0を押さずに小数点を押した場合、小数点の前に0をつける必要があります。

calculator.swift
  @IBAction func equal(_ sender: Any) {
        nums.append(Double(str_num)!)
        for i in 0..<signals.count{
            if signals[i] == "×"{
                nums[i + 1] *= nums[i]
                temporarys.append(i)
            }else if signals[i] == "÷"{
                nums[i + 1] = nums[i] / nums[i + 1]
                temporarys.append(i)
            }
        }
        for i in 0..<temporarys.count{
            nums.remove(at:temporarys[i]-count)
            signals.remove(at:temporarys[i]-count)
            count += 1
            }
        for i in 0..<signals.count{
            if signals[i] == "+"{
                nums[i + 1] += nums[i]

            }else if signals[i] == "-"{
                nums[i + 1] = nums[i] - nums[i + 1]
            }
        }
        ans = String(nums.last!)
        if String(ans.suffix(2)) == ".0" {
            ans = String(ans[..<ans.index(ans.startIndex, offsetBy: ans.count - 2)])
        }
        Ans.text = "=\(ans)"
    }

最後に「=」を押された時の計算です。ここが一番手こずりました

まず最初のnums.appendについて
僕のコードの書き方だと記号を押してからsig関数でnums.appendするので
1+2+3 の 3 が numsに追加できないまま計算することになるので、追加する。

次に、掛け算と割り算を優先して計算する必要があるので リストsignals の要素数分
for文で回す。
計算のアルゴリズムとしては、例えば
Ans = 2+3*4*5+6 = 68 の時 nums = [2,3,4,5,6] 、signals = [+,*,*,+]となっている
まず掛け算を見つけたので計算してnums[i+1] = nums[i] * nums[i + 1]をするので
nums = [2,3,12,5,6] 、signals = [+,*,*,+]

そしてtemporarysにiを追加する これはのちにnums[i]を削除する必要があるからです。
それからnums = [2,3,12,60,6] 、 signals = [+,*,*,+]
ここで掛け算の処理が終わってtemporarys=[1,2]がはいっているので
nums[1],nums[2],signals=[1],signals=[2]をリストから削除する

nums = [2,60,6] 、signals = [+,+]

そして「+」「-」の計算をし リストnumsの最後のインデックスが答えとなる

nums = [2,62,68] 、signals = [+,+]

なのでAnsにDoblesからStringに変換した答えを代入して答えを表示する。

これをシュミレーターで実行してみると

スクリーンショット 2019-11-01 12.17.54.png

うまく実行できていますね。

ここで今気づいた点ですが 2.3 + 5.3 + 3.3 = 10.899999.....
となったので次回 この点を改善し ()記号を実装していこうと思います。
また1~9のボタンと四則演算記号ボタンの入力コードが省略できそうなので
チャレンジしてみる。

初心者なので間違っているかもしれないが、誰かの役に立てれば嬉しい。

以下全コード

calculator.swift
import UIKit

class ViewController: UIViewController {
    var str_num:String = "" //計算に使用する変数
    var formula:String = "" //式を表示するだけ
    var nums:[Double] = [] //数値を入れるリスト
    var signals:[String] = [] //四則演算記号を入れるリスト
    var temporarys:[Int] = [] //一時的に記憶しておくリスト
    var count:Int = 0 //リストを消去する際に使用
    var ans:String = "" //答えを代入、前回の答えを使用できる
    var signal_on_off = false //on == true , off == false 記号のダブりを防ぐ

    func calc(num:String){
        formula += num
        str_num += num
        Formula.text = formula
        signal_on_off = false
    }
    func sig(signal:String){
        if signal_on_off == false{
            formula += signal
            nums.append(Double(str_num)!)
            str_num = ""
            signals.append(signal)
            Formula.text = formula
        }
        signal_on_off = true
    }

    @IBAction func AC(_ sender: Any) {
        str_num = ""
        formula = ""
        nums.removeAll()
        signals.removeAll()
        temporarys.removeAll()
        count = 0
        Formula.text = "0"
        Ans.text = "0"
    }
    @IBAction func equal(_ sender: Any) {
        nums.append(Double(str_num)!)
        print(nums)
        for i in 0..<signals.count{
            if signals[i] == "×"{
                nums[i + 1] *= nums[i]
                print(nums)
                temporarys.append(i)
            }else if signals[i] == "÷"{
                nums[i + 1] = nums[i] / nums[i + 1]
                temporarys.append(i)
            }
        }
        for i in 0..<temporarys.count{
            nums.remove(at:temporarys[i]-count)
            signals.remove(at:temporarys[i]-count)
            print(nums)
            count += 1
            }
        for i in 0..<signals.count{
            if signals[i] == "+"{
                nums[i + 1] += nums[i]

            }else if signals[i] == "-"{
                nums[i + 1] = nums[i] - nums[i + 1]
            }
        }
        ans = String(nums.last!)
        if String(ans.suffix(2)) == ".0" {
            ans = String(ans[..<ans.index(ans.startIndex, offsetBy: ans.count - 2)])
        }
        Ans.text = "=\(ans)"
    }
    @IBAction func divide(_ sender: Any) {
        sig(signal:"÷")
    }
    @IBAction func times(_ sender: Any) {
        sig(signal:"×")
    }
    @IBAction func minus(_ sender: Any) {
        sig(signal:"-")
    }
    @IBAction func plus(_ sender: Any) {
        sig(signal:"+")
    }
    @IBAction func nine(_ sender: Any) {
        calc(num: "9")
    }
    @IBAction func eight(_ sender: Any) {
        calc(num: "8")
    }
    @IBAction func seven(_ sender: Any) {
        calc(num: "7")
    }
    @IBAction func six(_ sender: Any) {
        calc(num: "6")
    }
    @IBAction func five(_ sender: Any) {
        calc(num: "5")
    }
    @IBAction func four(_ sender: Any) {
        calc(num: "4")
    }
    @IBAction func three(_ sender: Any) {
        calc(num: "3")
    }
    @IBAction func two(_ sender: Any) {
        calc(num: "2")
    }
    @IBAction func one(_ sender: Any) {
        calc(num: "1")
    }
    @IBAction func zero(_ sender: Any) {
        if str_num == "" || str_num == "0"{
        }else{
        calc(num: "0")
        }
    }

    @IBAction func decimal_point(_ sender: Any) {
        if str_num.contains(".") == true{
        }else if str_num == ""{
            calc(num:"0.")
        }else{
            calc(num:".")
        }
    }
    @IBOutlet weak var Ans: UILabel!
    @IBOutlet weak var Formula: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者が作る高機能電卓作り No,1 忘備録

はじめに
半年ほどpythonでディープラーニングなどを勉強していたけど
複雑になってきてわからなくなったので、
気分転換にswiftに手を出して練習がてらに高機能電卓を作ってみた。

swift勉強期間 1週間ほど

今回の目的は
1、電卓を作る
2、掛け算と割り算を優先して計算する
3、小数点の計算ができる
4、()内を最優先して計算する
5、DELを作る
6、前回の答えを計算式中に組み込む
7、計算式中の値を変更する

今パッと思いついた物を書いただけなので変更する可能性があります。

現在の進度としては「3」の小数点の計算まで進んでいます。

Main.storyboardはこのようになっています。
スクリーンショット 2019-11-01 10.59.23.png
数値や記号を押すたびにFormula.textに計算式を表示させます

それではコードの説明です。
(最後に全コードを載せておきます)

はじめに変数の宣言から

calclater
import UIKit

class ViewController: UIViewController {
    var str_num:String = "" //計算に使用する変数
    var formula:String = "" //式を表示するだけ
    var nums:[Double] = [] //数値を入れるリスト
    var signals:[String] = [] //四則演算記号を入れるリスト
    var temporarys:[Int] = [] //一時的に記憶しておくリスト
    var count:Int = 0 //リストを消去する際に使用
    var ans:String = "" //答えを代入、前回の答えを使用できる
    var signal_on_off = false //on == true , off == false 記号のダブりを防ぐ

変数 temporarys と ans , signal_on_off はなぜ作ったかは、のちにわかります。

nums:[Double]は小数点の計算をするために[Int]ではなく、[Double]をつかいました。

また今回電卓を作るにあたって僕が考えたアルゴリズムは
Ans = 1+2+3*4 を計算する時
nums = [1,2,3,4]
signals = [+,+,*]
をリストに追加して
signals[i]の時nums[i]とnums[i + 1]を計算するという方法です。
これが最適解とはわかりませんが僕はこれで行こうと思います。

calclater
 @IBAction func one(_ sender: Any) {
      calc(num: "1")
  }
  @IBAction func zero(_ sender: Any)
    if str_num == "" || str_num == "0"{
       }else{
       calc(num: "0")
       }
  }

ボタンについて1から9はcalc関数に引数を入れていますが
0だけは厄介で、例えば 000+002+200 は間違いで
0+2+200 にしなければいけないので
calc関数をみるとわかりますがstr_num が""の時と"0"の時は何も実行しないようにします。

calclater
  @IBAction func plus(_ sender: Any) {
        sig(signal:"+")

四則演算記号はsig関数に引数を渡しています。
plas,minus,times,divideは上と引数以外 同じです。

calclater
  func calc(num:String){
        formula += num
        str_num += num
        Formula.text = formula
        signal_on_off = false
    }
    func sig(signal:String){
        if signal_on_off == false{
            formula += signal
            nums.append(Double(str_num)!)
            str_num = ""
            signals.append(signal)
            Formula.text = formula
        }
        signal_on_off = true
    }

まずcalc関数のformulaから 文字列の足し算なのでACを押されるまではボタンが押されるたびに足し合わせることで計算式を表現できる。

次にstr_numについてformulaと同じように見えるが四則演算記号を押されると
変数 str_numは リストnums に追加され str_num = "" とされる。
この操作は sig関数で実行される。

Formula.textで計算式を表示

signals_on_offは例えば、 1++2*-3+*/-4 = ????
など記号が重複することを防ぐため、記号が押されるとtrueが代入され記号の入力を受け付けない
が数値が押されるとfalseが代入され記号の入力ができるようになる。

次にsig関数について
基本先ほどにも書いた通りだが

nums.append(Double(str_num)!)は
str_numをStringからDoubleへ変更させます。この時nilは存在しないので「!」を最後につけます。

calclater
  @IBAction func AC(_ sender: Any) {
        str_num = ""
        formula = ""
        nums.removeAll()
        signals.removeAll()
        temporarys.removeAll()
        count = 0
        Formula.text = "0"
        Ans.text = "0"
    }
  @IBAction func decimal_point(_ sender: Any) {
        if str_num.contains(".") == true{
        }else if str_num == ""{
            calc(num:"0.")
        }else{
            calc(num:".")
        }
    }

次にACと小数点について
ACは見ての通りAllClearします

小数点では例えば 0.00.21.3 + 1.0.2 など どこが小数点かわからなくなるのを防ぐために
str_numのなかに1つ「.」が存在すれば(true)何も実行しない。

また 1 + .01 など0を押さずに小数点を押した場合、小数点の前に0をつける必要があります。

calclater
  @IBAction func equal(_ sender: Any) {
        nums.append(Double(str_num)!)
        for i in 0..<signals.count{
            if signals[i] == "×"{
                nums[i + 1] *= nums[i]
                temporarys.append(i)
            }else if signals[i] == "÷"{
                nums[i + 1] = nums[i] / nums[i + 1]
                temporarys.append(i)
            }
        }
        for i in 0..<temporarys.count{
            nums.remove(at:temporarys[i]-count)
            signals.remove(at:temporarys[i]-count)
            count += 1
            }
        for i in 0..<signals.count{
            if signals[i] == "+"{
                nums[i + 1] += nums[i]

            }else if signals[i] == "-"{
                nums[i + 1] = nums[i] - nums[i + 1]
            }
        }
        ans = String(nums.last!)
        if String(ans.suffix(2)) == ".0" {
            ans = String(ans[..<ans.index(ans.startIndex, offsetBy: ans.count - 2)])
        }
        Ans.text = "=\(ans)"
    }

最後に「=」を押された時の計算です。ここが一番手こずりました

まず最初のnums.appendについて
僕のコードの書き方だと記号を押してからsig関数でnums.appendするので
1+2+3 の 3 が numsに追加できないまま計算することになるので、追加する。

次に、掛け算と割り算を優先して計算する必要があるので リストsignals の要素数分
for文で回す。
計算のアルゴリズムとしては、例えば
Ans = 2+3*4*5+6 = 68 の時 nums = [2,3,4,5,6] 、signals = [+,*,*,+]となっている
まず掛け算を見つけたので計算してnums[i+1] = nums[i] * nums[i + 1]をするので
nums = [2,3,12,5,6] 、signals = [+,*,*,+]

そしてtemporarysにiを追加する これはのちにnums[i]を削除する必要があるからです。
それからnums = [2,3,12,60,6] 、 signals = [+,*,*,+]
ここで掛け算の処理が終わってtemporarys=[1,2]がはいっているので
nums[1],nums[2],signals=[1],signals=[2]をリストから削除する

nums = [2,60,6] 、signals = [+,+]

そして「+」「-」の計算をし リストnumsの最後のインデックスが答えとなる

nums = [2,62,68] 、signals = [+,+]

なのでAnsにDoblesからStringに変換した答えを代入して答えを表示する。

これをシュミレーターで実行してみると

スクリーンショット 2019-11-01 12.17.54.png

うまく実行できていますね。

ここで今気づいた点ですが 2.3 + 5.3 + 3.3 = 10.899999.....
となったので次回 この点を改善し ()記号を実装していこうと思います。
また1~9のボタンと四則演算記号ボタンがの入力コードが省略できそうなので
チャレンジしてみる。

初心者なので間違っているかもしれないが、誰かの役に立てれば嬉しい。

以下全コード

calclater
import UIKit

class ViewController: UIViewController {
    var str_num:String = "" //計算に使用する変数
    var formula:String = "" //式を表示するだけ
    var nums:[Double] = [] //数値を入れるリスト
    var signals:[String] = [] //四則演算記号を入れるリスト
    var temporarys:[Int] = [] //一時的に記憶しておくリスト
    var count:Int = 0 //リストを消去する際に使用
    var ans:String = "" //答えを代入、前回の答えを使用できる
    var signal_on_off = false //on == true , off == false 記号のダブりを防ぐ

    func calc(num:String){
        formula += num
        str_num += num
        Formula.text = formula
        signal_on_off = false
    }
    func sig(signal:String){
        if signal_on_off == false{
            formula += signal
            nums.append(Double(str_num)!)
            str_num = ""
            signals.append(signal)
            Formula.text = formula
        }
        signal_on_off = true
    }

    @IBAction func AC(_ sender: Any) {
        str_num = ""
        formula = ""
        nums.removeAll()
        signals.removeAll()
        temporarys.removeAll()
        count = 0
        Formula.text = "0"
        Ans.text = "0"
    }
    @IBAction func equal(_ sender: Any) {
        nums.append(Double(str_num)!)
        print(nums)
        for i in 0..<signals.count{
            if signals[i] == "×"{
                nums[i + 1] *= nums[i]
                print(nums)
                temporarys.append(i)
            }else if signals[i] == "÷"{
                nums[i + 1] = nums[i] / nums[i + 1]
                temporarys.append(i)
            }
        }
        for i in 0..<temporarys.count{
            nums.remove(at:temporarys[i]-count)
            signals.remove(at:temporarys[i]-count)
            print(nums)
            count += 1
            }
        for i in 0..<signals.count{
            if signals[i] == "+"{
                nums[i + 1] += nums[i]

            }else if signals[i] == "-"{
                nums[i + 1] = nums[i] - nums[i + 1]
            }
        }
        ans = String(nums.last!)
        if String(ans.suffix(2)) == ".0" {
            ans = String(ans[..<ans.index(ans.startIndex, offsetBy: ans.count - 2)])
        }
        Ans.text = "=\(ans)"
    }
    @IBAction func divide(_ sender: Any) {
        sig(signal:"÷")
    }
    @IBAction func times(_ sender: Any) {
        sig(signal:"×")
    }
    @IBAction func minus(_ sender: Any) {
        sig(signal:"-")
    }
    @IBAction func plus(_ sender: Any) {
        sig(signal:"+")
    }
    @IBAction func nine(_ sender: Any) {
        calc(num: "9")
    }
    @IBAction func eight(_ sender: Any) {
        calc(num: "8")
    }
    @IBAction func seven(_ sender: Any) {
        calc(num: "7")
    }
    @IBAction func six(_ sender: Any) {
        calc(num: "6")
    }
    @IBAction func five(_ sender: Any) {
        calc(num: "5")
    }
    @IBAction func four(_ sender: Any) {
        calc(num: "4")
    }
    @IBAction func three(_ sender: Any) {
        calc(num: "3")
    }
    @IBAction func two(_ sender: Any) {
        calc(num: "2")
    }
    @IBAction func one(_ sender: Any) {
        calc(num: "1")
    }
    @IBAction func zero(_ sender: Any) {
        if str_num == "" || str_num == "0"{
        }else{
        calc(num: "0")
        }
    }

    @IBAction func decimal_point(_ sender: Any) {
        if str_num.contains(".") == true{
        }else if str_num == ""{
            calc(num:"0.")
        }else{
            calc(num:".")
        }
    }
    @IBOutlet weak var Ans: UILabel!
    @IBOutlet weak var Formula: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む