- 投稿日:2019-11-01T22:23:09+09:00
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.hogehogeUIApplication.shared.connectedScenesはマルチウィンドウに表示しているすべてのSceneを取得してしまう.そこで現在表示しているstoryboardが
$0.session.configuration.storyboardと等しいことを確認することで取得可能になる.
他にいい方法とかあれば教えてほしい....
- 投稿日:2019-11-01T21:30:26+09:00
【Xcode11.1, Swift】Deployment Target iOS13.1でも、実機iOS12.4でビルドする方法(図解)
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バージョンに合わせてビルドする。
Project > info > Deployment Info Target >
iOS Deployment Target 12.4をクリック
3. ビルドを実行すると、バージョンに対応していないため、エラーが起こります。
エラーが起こったファイルは、SenceDelegate.swiftとAppleDelegate.swift。
4. SenceDelegate.swiftとAppleDelegate.swiftを全てFixする
最後まで続けて、修正できない箇所があれば、コメントアウトします。
今回は、AppleDelegate.swiftにあった。全部Fix出来たら、ビルドします。
上手くビルド出来たら成功です。もし、実機でビルドが上手くいかない場合
ログに
[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?これでもう一度を試します。
qiita用
— Koshi⚔王国騎士?ロボットVTuber (@Koshi_VTuber) November 1, 2019
【Xcode11.1, Swift】Deployment Target iOS13.1でも、実機iOS12.4でビルドする方法(図解) pic.twitter.com/gjdwpKKqCjこれでビルドに成功しました。
†■━ †■━ †■━ †■━ †■━ †■━ †■━
もし、この記事が役に立ったら、
「いいね」「ストック」
押してくれ??以上、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_MyJankenthe 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/
- 投稿日:2019-11-01T20:56:34+09:00
SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた。
SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた
はじめに
今回はFirebaseがとても使いやすいと聞いたので試しに簡単にアプリを作ってみました。
備忘録作成アプリというのは「このエラー前も出たけどなんだっけ」だったり「このライブラリのこの関数どうやって使うんだっけ」と言ったときもう一度調べ直すのは面倒臭いので、それを備忘録として作成できるアプリです。この記事ではどのように開発していったか説明していきます。まだ初心者ですので指摘等あったらお願いします。PythonもやっているのでPythonの備忘録を例としてやっていきます。
ユーザー管理
ログイン画面
@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でパスワードとメールアドレスが正しいかを確認し、正しくない場合はアラートを出すようにしています。登録画面
@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)でスタート画面に戻るようにします。これをしないとそのまま操作ができてしまうからです。備忘録画面
左が備忘録一覧で、右が備忘録追加画面になっています。
データの読み込み
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が入っている時と何もない時にアラートを出すようにしています。言葉追加画面
ここでは備忘録内に実際に言葉を追加する画面です。
ここの処理は備忘録を追加する処理と似ているので最後にコードを載せるだけにしておきます。
デモ
参考
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 } }
- 投稿日:2019-11-01T20:56:34+09:00
SwiftでFirebaseを使って簡単な備忘録作成アプリを作ってみた
はじめに
今回はFirebaseがとても使いやすいと聞いたので試しに簡単にアプリを作ってみました。
備忘録作成アプリというのは「このエラー前も出たけどなんだっけ」だったり「このライブラリのこの関数どうやって使うんだっけ」と言ったときもう一度調べ直すのは面倒臭いので、それを備忘録として作成できるアプリです。この記事ではどのように開発していったか説明していきます。まだ初心者ですので指摘等あったらお願いします。PythonもやっているのでPythonの備忘録を例としてやっていきます。
ユーザー管理
ログイン画面
@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でパスワードとメールアドレスが正しいかを確認し、正しくない場合はアラートを出すようにしています。登録画面
@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)でスタート画面に戻るようにします。これをしないとそのまま操作ができてしまうからです。備忘録画面
左が備忘録一覧で、右が備忘録追加画面になっています。
データの読み込み
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が入っている時と何もない時にアラートを出すようにしています。言葉追加画面
ここでは備忘録内に実際に言葉を追加する画面です。
ここの処理は備忘録を追加する処理と似ているので最後にコードを載せるだけにしておきます。
デモ
参考
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 } }
- 投稿日:2019-11-01T17:18:54+09:00
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を配置していると起きる
- 実機で実行した場合のみに起きる
対策
- UITextViewをコードで配置するように修正
- Xcode 11.1 を使う(ここでDL https://developer.apple.com/download/more/ )
リンク
- 投稿日:2019-11-01T17:13:38+09:00
初めてのGit
今年(2019年)の4月に中途入社した、ペーペー社員です。
初めてちゃんとした?プロジェクトに入り、Gitを触る機会が増えた為、アプトプット。
中途とはいえ、業界未経験の私には、Gitがなんなのかさっぱり・・・
今回は初投稿という事で、Markdown記法の勉強も兼ねて、Gitの基本的用語から。
※SourceTree(Version:3.1.1)を使用ローカルリポジトリ
自分のマシン上にあるリポジトリ
リモートリポジトリ
ネットワーク上にあるリポジトリ
Commit(コミット)
ファイルやデュレクトリの変更をローカルリポジトリ内で変更を行う事
Push(プッシュ)
ローカルリポジトリの修正内容をリモートリポジトリに反映させる事
Pull(プル)
Fetch(フェッチ)
リモートリポジトリの最新データを取得する事
※Pullと違ってローカルリポジトリに取り込んだりしない
Stash(スタッシュ)
作業中の変更を一時退避させる事
※差し込み作業が入った時に、現時点ではCommitをしたくない時に使用
Cherry-pick(チェリーピック)
特定のCommitした内容だけをピックアップして取り込む事(複数OK)
Conflict(コンフリクト)
同じファイルに同じ箇所の変更が同時に起こる事
// コンフリクトの見方 <<<<<< HEAD 自分の変更内容 ===== コンフリクトが起きているブランチ master >>>>>>↓
<<<<<< HEAD private func hogeMethod() { print("hoge") } ===== private func fugaMethod() { print("fuga") } master >>>>>>どちらが正しいのか修正する必要がある
基本的な用語と意味を理解しておくと、操作しやすくなります。
学んだ事や理解したことがどんどん蓄積されていくので、おいおい投稿していこうと思います!
では、また!
- 投稿日:2019-11-01T13:17:21+09:00
�初心者が作るSwiftでの高機能電卓の作り方 No�,1 忘備録
はじめに
半年ほどpythonでディープラーニングなどを勉強していたけど
複雑になってきてわからなくなったので、
気分転換にswiftに手を出して練習がてらに高機能電卓を作ってみた。swift勉強期間 1週間ほど
今回の目的は
1、電卓を作る
2、掛け算と割り算を優先して計算する
3、小数点の計算ができる
4、()内を最優先して計算する
5、DELを作る
6、前回の答えを計算式中に組み込む
7、計算式中の値を変更する今パッと思いついた物を書いただけなので変更する可能性があります。
現在の進度としては「3」の小数点の計算まで進んでいます。
Main.storyboardはこのようになっています。
数値や記号を押すたびにFormula.textに計算式を表示させますそれではコードの説明です。
(最後に全コードを載せておきます)はじめに変数の宣言から
calculator.swiftimport 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.swiftfunc 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に変換した答えを代入して答えを表示する。
これをシュミレーターで実行してみると
うまく実行できていますね。
ここで今気づいた点ですが 2.3 + 5.3 + 3.3 = 10.899999.....
となったので次回 この点を改善し ()記号を実装していこうと思います。
また1~9のボタンと四則演算記号ボタンの入力コードが省略できそうなので
チャレンジしてみる。初心者なので間違っているかもしれないが、誰かの役に立てれば嬉しい。
以下全コード
calculator.swiftimport 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() } }
- 投稿日:2019-11-01T12:40:10+09:00
初心者が作る高機能電卓作り No,1 忘備録
はじめに
半年ほどpythonでディープラーニングなどを勉強していたけど
複雑になってきてわからなくなったので、
気分転換にswiftに手を出して練習がてらに高機能電卓を作ってみた。swift勉強期間 1週間ほど
今回の目的は
1、電卓を作る
2、掛け算と割り算を優先して計算する
3、小数点の計算ができる
4、()内を最優先して計算する
5、DELを作る
6、前回の答えを計算式中に組み込む
7、計算式中の値を変更する今パッと思いついた物を書いただけなので変更する可能性があります。
現在の進度としては「3」の小数点の計算まで進んでいます。
Main.storyboardはこのようになっています。
数値や記号を押すたびにFormula.textに計算式を表示させますそれではコードの説明です。
(最後に全コードを載せておきます)はじめに変数の宣言から
calclaterimport 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は上と引数以外 同じです。calclaterfunc 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に変換した答えを代入して答えを表示する。
これをシュミレーターで実行してみると
うまく実行できていますね。
ここで今気づいた点ですが 2.3 + 5.3 + 3.3 = 10.899999.....
となったので次回 この点を改善し ()記号を実装していこうと思います。
また1~9のボタンと四則演算記号ボタンがの入力コードが省略できそうなので
チャレンジしてみる。初心者なので間違っているかもしれないが、誰かの役に立てれば嬉しい。
以下全コード
calclaterimport 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() } }
























