【Xcode】UserDefaultsでログイン機能 - 本格版
前回の記事で、UserDefaultsで簡易的なログイン機能を実装する方法を紹介しました。
しかし、ユーザー名に関係なく次回以降は無条件でログインできてしまう欠点がありました。
今回は、初回時に入力したユーザー名を入力しない限りログインできない仕組みをご紹介します。
プロセス
既に新規登録したか?
↓→してないなら②へ
したなら①へ
①既に新規登録した
textFieldの入力値が正しいか?
↓
↓→正しい→ログイン成功
↓
間違い
↓
アラートで再入力を要求
②まだしてない
textFieldに入力したか?
↓
↓→した→新規登録完了
↓
してない
↓
アラートで再入力を要求
ソースコード
import UIKit import EMAlertController class ViewController: UIViewController,UITextFieldDelegate { @IBOutlet weak var textField: UITextField! @IBOutlet weak var loginButton: UIButton! override func viewDidLoad() { super.viewDidLoad() textField.delegate = self } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) //もしユーザー名が保存されてるなら if let _ = UserDefaults.standard.object(forKey: "userName") { //ボタンの文字を"ログイン"に変更 loginButton.setTitle("ログイン", for: .normal) //保存されてないなら } else { loginButton.setTitle("会員登録", for: .normal) } } @IBAction func login(_ sender: Any) { //既にログインしてるなら if let _ = UserDefaults.standard.object(forKey: "userName") { //もし入力した値が保存したユーザー名と同じなら if textField.text == UserDefaults.standard.object(forKey: "userName") as? String { //遷移 performSegue(withIdentifier: "goTimeLine", sender: nil) //違うなら } else { //アラート作成 let alert = EMAlertController(title: "ユーザー名が正しくありません。", message: "") //アクション作成 let action = EMAlertAction(title: "OK", style: .cancel) //アラートにアクションを追加する alert.addAction(action: action) //アラートを表示 present(alert, animated: true, completion: nil) } //新規登録する場合 } else { //もしユーザー名が記入されてるなら if textField.text != "" { //UserDefaultsの変数 let ud = UserDefaults.standard //ユーザー名をUDに保存 ud.set(textField.text, forKey: "userName") //画面遷移 performSegue(withIdentifier: "goTimeLine", sender: nil) //もしユーザー名が記入されてないなら } else { //アラート let alert = EMAlertController(title: "ユーザー名を入力してください", message: "") //アクション let action = EMAlertAction(title: "OK", style: .cancel) //アラートにアクションを追加する alert.addAction(action: action) //アラートを表示 present(alert, animated: true, completion: nil) } } } //画面をタップするとキーボードが閉じる override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { textField.resignFirstResponder() } }
【Swift】tableViewでセルを表示する
完成画面
必要なプロセス
プロセス - StoryBoard編
- tableView、tableViewCellを配置
- tableViewのIdentifierを設定
- tableViewにOutletで紐付け
- tableViewCellの上にUILabelを配置
- UILabelのtagを1に設定
1.
2.
5.
プロセス - ViewContoller編
- UITableViewDelegate,UITableViewDataSourceを記述
- viewDidLoad()でdelegate,datasourceを宣言..?
- 表示したいテキストを配列で用意(今回はtextという名前の配列)
- セルの表示に必要な4つの関数を用意(numberOfSections、numberOfRowsInSection、cellForRowAt、heightForRowAt)
1.
numberOfSections:セルを分類するカテゴリの数
今回は分類しないので return 1
numberOfRowsInSection:セルの数
text配列の要素数を指定
cellForRowAt:セルを生成に関わる関数
StoryBoardで設定したセルのIdentifierを記述
//で設定したtagでUILabelを生成(今回は1)
heightForRowAt:1つのセルの高さ
お好みで
ソースコード
import UIKit class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { @IBOutlet weak var tableView: UITableView! let text = ["one","two","three","four","five","six"] override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self tableView.dataSource = self // Do any additional setup after loading the view, typically from a nib. } func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return text.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) let textLabel = cell.viewWithTag(1) as! UILabel textLabel.text = text[indexPath.row] return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 62 } }
※補足:セル上にテキストを表示させる方法について
セルにテキストを表示する場合、
- 直接代入:cell.textLabel?.text = “文字列”
- UILabel:cell.viewWithTag() as! UILabel
- カスタムセル
の3通り。
今回は、セルの上に配置したUILabelで、セルにテキストを表示させた。
注意点としては、セルの配置したUILabelを紐付けすることができないこと。
ImageViewはかろうじて配置できるが、それ以外はおそらく不可能。
なので、tagを指定し、cell.viewWithTagによって、tagで指定したUI部品を変数で生成する必要がある。
【teratail】Firebaseに保存されてるデータの受け取り方
自分でFirebaseと連携する機能を実装した際、思い通りに動作しなかったので、復習として書きます。
質問
実現したいこと
Firebaseに保存した全てのデータをタイムライン形式でセルに表示したい
困っていること
Firebaseに保存されてるはずなのに、セル(データ)が1つしか表示されない。
解決方法
原因は、データがまとめて格納されたposts配列をfor文内に書いてしまい、せっかく受け取ったデータを消してしまったことです。
なので、posts配列をfor文に入る前に初期化すればいいのです。
解決後のコード
ref.child("post").observeSingleEvent(of: .value) { (snap,error) in //Firebaseのデータが入ったsnapを配列に格納 let snapData = snap.value as? [String:NSDictionary] if snapData == nil { return } //NSObjectクラスの”Post.swift”に表示したいデータを格納する変数を宣言してます //1つのセルに表示したいデータ群をposts配列に格納していきます self.posts = [Post]() for (_,post) in snapData! { //解決前は、ここにposts配列の初期化をしてました //self.posts = [Post]() //取得したデータを毎回posstが受け取ります //新品のデータを受け取るために毎回初期化します self.posst = Post() if let title = post["title"] as? String, let sentence = post["sentence"] as? String, let pictureURLString = post["pictureURLString"] as? String, let category = post["category"] as? String { self.posst.title = title self.posst.sentence = sentence self.posst.pictureURLString = pictureURLString self.posst.category = category } self.posts.append(self.posst) print(self.posts) } self.tableView.reloadData() }
解決前は、
1つ目のデータを受け取る
↓
初期化
↓
2つ目のデータを受け取る
↓
初期化
↓
・
・
というプロセスで、最終的にposts配列には最後に入れたデータしか残りませんでした。
しかし、解決後は、
初期化
↓
1つ目のデータを受け取る
↓
2つ目のデータを受け取る
↓
3つ目のデータを受け取る
というプロセスに変わり、全てのデータをきちんと格納できています。
【Swift】UserDefaultsでログイン機能 - 簡易版
難しい機能を使わず、UserDefaultsで簡単にログイン機能を実装します。
ソースコード
初回ログイン時
//StoryBoardに設置したUIButton @IBAction func login(_ sender: Any) { //もしtextFieldに文字が入力されてるなら if textField.text != "" { let ud = UserDefaults.standard //ユーザー名をUDに保存 ud.set(textField.text, forKey: "userName") //次の画面に遷移するなど performSegue(withIdentifier: "goTimeLine", sender: nil) //もしtextFieldに何も入力されてなければ } else { //アラート let alert = EMAlertController(title: "ユーザー名を入力してください", message: "") //アクション let action = EMAlertAction(title: "OK", style: .cancel) //アラートにアクションを追加する alert.addAction(action: action) //アラートを表示 present(alert, animated: true, completion: nil) } }
UIButtonを設置し、”userName”キーとして保存してます。
何も入力してない時のアラートをEMAlertControllerで実装しました。
UIAlertActionの方だと、アラートを生成するコードが長く、コードを短くしてシンプルにまとめたほうが見栄えが綺麗になるので、EMの方を採用しました。
EMControllerをimportして、たった4行で実装できるのはめっちゃ便利ですよね。
次回ログインを省略
override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) //もしユーザー名が保存されてるなら if let _ = UserDefaults.standard.object(forKey: "userName") { performSegue(withIdentifier: "goTimeLine", sender: nil) } }
僕は最初、viewWillAppear()の中に書きましたが、ログイン画面を省略できませんでした。
teratailで質問すると、
「viewWillAppear()はviewが生成される直前に呼ばれるので、遷移する処理は実行されません。」
「viewDidAppear()で実装してください」
と言われ、viewDidAppear()に書いたら、うまく動作しました。
viewのライフサイクルを学ばないといけませんねぇ。
デメリット
上記のコードの場合、「1度ログインしたユーザーは無条件でログインできる」こと。
例えば、一般的なサイトの場合、
会員登録する
↓
次回ログイン時に、個人情報を入力
↓→合致しない場合、再ログインが要求される
保存した個人情報と合致すればログインされる
というプロセスになっています。
対して、今回の記事で紹介したUserDefaultsによるログイン機能1は、入力したユーザー名は保存されるけど、次回以降は入力したデバイスを使えば誰であろうと無条件でログインできてしまいます。
今回のコードは、タイムラインでコメントなどを表示するアプリで使用したコードです。
別の記事で、入力したユーザー名がUserDefaultsに合致するかを判断する機能をご紹介します。
まとめ
100万人ものユーザーが利用するサービスだと、FirebaseやAWSなどの大規模なデータベースにユーザーの個人情報を保存するのでしょうが、個人で作るようなアプリならFirebaseやUserDefalutsで十分です。
別にリリースするつもりがなければ、UserDefaultsで事足りるでしょう。