※こちらは自分の備忘録も兼ねて作成していますので、もし紛らわしい表現や記述の誤り等を発見した場合にはコメント欄等でお申し付け頂ければと思います。
前回からの復習:疎通試験が終わったのでいざ機能の実装へ!
前回の内容はこちらになりますので、まだParseの導入&疎通試験がお済みでない方は下記をご参考にして導入をしてみてください。
今回はParse.comが提供してくれている認証画面を用いてユーザー登録&ユーザーログイン機能のおおまかな部分を実際に作成していきます。0から作ろうとするとなると、サーバーサイドのプログラムの実装やアプリ側でもUIを作成する必要があるため、きっちり作り込みをしようとすると、しなければいけないことが増えますが、今回はサンプルアプリということもありますので、
- メインの会員登録・会員認証の部分
- ParseUIのデフォルトの見た目のカスタマイズ
をしていく予定です。
ParseUIとはParse.comが提供してくれているUIに関するフレームワーク
Parse.comでユーザー認証機能を作る場合は、主な方法としては
- 自前でViewを作成し認証の機能を作成する
- ParseUI.frameworkのUIをカスタマイズして認証機能を作成する
の2通りの方法があります。
(前者の方法で実装する際の参考資料)
Swift: Create user sign up based app with Parse.com using PFUser
(後者の方法で実装する場合の参考資料)
Parse Tutorial: Customizing Your Twitter/Facebook Signup/Login Views
今回は、Twitter / Facebookによる認証部分は省いてParse独自の認証機能を活用するため、後者の方の実装をしていく予定です。
※ 参考資料に関しては、英語の記事ですので「エッ!そんな!」と一見驚くかもしれませんが、サンプルコードや実装のポイントになる部分の解説がありますので、実際に作る前に一読しておくとさらに理解が深まると思います。(今回はこの内容をベースにして要点になる部分だけをピックアップして解説&実装を進めていく方針です)
準備
まずは下記のように、ViewControllerのレイアウトを作成します。
※本サンプルではAutoLayoutを利用しています。
今回の認証で利用するコントローラーは ViewController.swift
になります。また、このコントローラーの遷移先を DisplayController.swift
を作成します(こちらはデバッグログを仕込むだけです)。
1. ViewController.swiftのレイアウトを決定
今回作成する画面は下記のようになります。

ViewControlle.swiftのStoryboardに配置している部品の概要としては、
- タイトルを表示するUILabel
- 画像を表示するUIImageView
- アプリの使い方に関する文言を掲載するUITextView
- ログイン・非ログインに遷移先が変わるUIButton
- ログイン・非ログインに遷移先が変わるUIButtonのIBAction
の5つをStoryBoardへIBOutletで各パーツを配置していって下さい。
(配置の方法は現時点のサンプルソース内のStoryboardをGithubで確認してみて下さい)
■ この画面のSrotyboard
※今回はAutoLayoutを使用してレイアウトを作成しています。
//※ ParseとParseUIのインポートを忘れずに行って下さい
--- (省略) ---
//※ 下準備の一覧
//1. Asset内にテキストファイル入れておく
//2. UITextFieldはSelectable・Editableのチェックをはずし、Linksのチェックを入れる
//3. parse_spa_material内の画像をAssets.xcassetsへ追加する
//4. UIImageViewにcafe_cake.pngを追加し、ModeはAspectFillを選択する
//UITextViewに表示する内容を格納するメンバ変数
var guidanceText: String!
//IBOutletでの接続パーツ
@IBOutlet weak var topImageView: UIImageView!
@IBOutlet weak var guideTextView: UITextView!
@IBOutlet weak var goDisplayButton: UIButton!
またUITextView内の文字内容を変更するために、下記のような構造体を用意してあげます。
この構造体内にはHTMLタグを使用した文字列が入っています。
(※この書き方自体は全然スマートじゃない感じでもあるので、もしもっとスマートに欠ける場合にはカスタマイズして書いて頂ければと思います。)
struct LoginText {
static func notLoginContract() -> String {
return (非ログイン時のテキストの一覧)
}
static func alreadyLoginContract(username: String) -> String {
return (ログイン時のテキストの一覧)
}
}
2. Parseでのログイン時と非ログイン時のボタン処理の切り分けを行う
レイアウトが作成とテキストフィールドに入る文言がここで決まりましたので、実際に状態によって表示を切り分ける処理を記述していきます。
PFUser.currentUser()
で現在ログインしているか否かの情報を取得できるので、
-
PFUser.currentUser() == nil
の場合はUITextViewのテキストは非ログイン時のものを表示し、ボタンについてもParseのログイン画面用の文言に変更する - 既にログイン済みの場合はUITextViewのテキストはログイン済みの時のものを表示し、ボタンについても次の画面(※まだ作成途中)に遷移用の文言に変更する
の場合分けをした処理を記述します。
//Viewが出現したタイミングに行われる処理
override func viewWillAppear(animated: Bool) {
//ログインしていない場合
if PFUser.currentUser() == nil {
//非ログイン時の表記を設定
self.guidanceText = LoginText.notLoginContract()
self.goDisplayButton.setTitle("会員登録をする", forState: .Normal)
//ログインをしている場合
} else {
//ログイン時の表記を設定
self.guidanceText = LoginText.alreadyLoginContract(PFUser.currentUser()!.username!)
self.goDisplayButton.setTitle("アプリをはじめる", forState: .Normal)
}
//テキストファイルをhtmlへコンバートする
do {
//正常処理
let htmlText: String = self.guidanceText
let encodedData = htmlText.dataUsingEncoding(NSUTF8StringEncoding)!
let attributedOptions : [String : AnyObject] = [
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding
]
let attributedString = try NSAttributedString(data: encodedData, options: attributedOptions, documentAttributes: nil)
self.guideTextView.attributedText = attributedString
} catch {
//例外処理
fatalError("テキストの変換に失敗しました")
}
}
ただ、この状態ですとUITexuViewに表示したテキストが一番下にいる時の状態で表示されてしまったため、レイアウト処理が完了した際にUITexuViewの中にあるテキストを一番上に表示している状態にしてあげます。
さらにAutoLayoutを使用した際の要素の変更や再配置・UIViewControllerのライフサイクルに関しては下記のリンクが参考になりました。
■ 参考リンク
- UIViewControllerのライフサイクル
- AutoLayoutで配置されたViewの実際のFrameを取得する方法
- UIViewのレイアウト周りの処理はviewDidLayoutSubViewsの内部でやった方がいい
//レイアウト処理が完了した際の処理
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//UITextViewの初期位置を戻す
guideTextView.setContentOffset(CGPointZero, animated: false)
}
そして上記でボタンやテキストの表示の切り分けを行ったようにボタンアクションに関しても同じくログイン状態で処理の切り分けを行います。
//会員登録または次の画面に遷移するアクション
@IBAction func goNextOperationAction(sender: UIButton) {
//ログインしていない場合
if PFUser.currentUser() == nil {
//ParseUIのログイン機能を呼び出す
self.loginParseUiContents()
//ログインをしている場合
} else {
//次の画面へ遷移する
self.displayAppliContents()
}
}
3. ParseUIで提供されているUIの色やロゴ等の基本的なパーツをカスタマイズするクラスの作成
今回はParseUIで提供されているログイン画面及びサインアップ画面に関して基本的なパーツに関して、作成するサンプルに合わせて見た目のカスタマイズを行っていこうと思います。
実際のカスタマイズの方法や画面の使用に関しては、下記の資料を参考にしました。
Customizing Parse Login in Swift
上記はLogin画面のカスタマイズ方法や設定の方法に関するStackOverFlowの記事になります(Swiftはおそらく1.2でのコードかと思いますのでその部分はご注意下さい)
またデフォルトのParseUIのログイン・サインアップ画面に画面にどんなパーツが配置されているかは下記のリンクが参考になりました。
■ ParseUIのデフォルトのLogin画面パーツに関する情報
■ ParseUIのデフォルトのSignUp画面パーツに関する情報
もっと突っ込んだ画面の中のソースに関しては下記のGithubでParseが公開しておりますのでこちらの情報を御一読いただくとしっかりと仕組みがわかるかもしれませんね。
※中身のソースはObjective-Cで書かれています。
まずは画面のカスタマイズをする上での実装方針としては、
- Login画面はPFLogInViewControllerを継承したクラスを作成し、パーツの配色変更やロゴの文字等を再配置を行う
- Signup画面はPFSignUpViewControllerを継承したクラスを作成し、パーツの配色変更やロゴの文字等を再配置を行う
を行っていくことになります。
今回行うカスタマイズとしては、
- 「Parse」ロゴ部分の文字の変更
- 背景画像の追加
- ボタンやテキストリンクまわりの配色変更
の3つになります。
下記に各クラスファイルを記載しますので、同様にカスタマイズを行っていく際には是非ご参考にしてみてください。
■ Loginに関するクラス
import Parse
import ParseUI
//Login画面のデザインを変更するためのクラス
class LoginViewController : PFLogInViewController {
var backgroundImage : UIImageView!;
override func viewDidLoad() {
super.viewDidLoad()
//ロゴ文言の設定
let newLogoText = UILabel()
newLogoText.textColor = ColorDefinition.colorWithHexString("#333333")
newLogoText.text = "Login Premium Cafe List"
newLogoText.font = UIFont.systemFontOfSize(CGFloat(24.0))
self.logInView?.logo = newLogoText
//背景イメージの設定
self.backgroundImage = UIImageView(image: UIImage(named: "cafe_exterior"))
self.backgroundImage.contentMode = UIViewContentMode.ScaleAspectFill
self.logInView!.insertSubview(self.backgroundImage, atIndex: 0)
//Log Inボタンの背景色の変更
self.logInView!.logInButton?.setBackgroundImage(nil, forState: .Normal)
self.logInView!.logInButton?.backgroundColor = ColorDefinition.colorWithHexString("#cc9933")
//Forgot Password?の文字色
self.logInView!.passwordForgottenButton?.setTitleColor(ColorDefinition.colorWithHexString("#666666"), forState: .Normal)
//Sign Upボタンの背景色の変更
self.logInView!.signUpButton?.setBackgroundImage(nil, forState: .Normal)
self.logInView!.signUpButton?.backgroundColor = ColorDefinition.colorWithHexString("#ffae00")
}
//レイアウト処理が完了した際の処理
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//ロゴ文言のサイズ設定
self.logInView!.logo!.sizeToFit()
let logoFrame = self.logInView!.logo!.frame
self.logInView!.logo!.frame = CGRectMake(
logoFrame.origin.x,
self.logInView!.usernameField!.frame.origin.y - logoFrame.height - 20,
self.logInView!.frame.width,
logoFrame.height
)
//背景イメージのサイズ設定
self.backgroundImage.frame = CGRectMake(0, 0, self.logInView!.frame.width, self.logInView!.frame.height)
}
}
■ Signupに関するクラス
import Parse
import ParseUI
//Signup画面のデザインを変更するためのクラス
class SignUpViewController : PFSignUpViewController {
var backgroundImage : UIImageView!;
override func viewDidLoad() {
super.viewDidLoad()
//ロゴ文言の設定
let newLogoText = UILabel()
newLogoText.textColor = ColorDefinition.colorWithHexString("#333333")
newLogoText.text = "Sign Up Premium Cafe List"
newLogoText.font = UIFont.systemFontOfSize(CGFloat(24.0))
self.signUpView?.logo = newLogoText
//背景イメージの設定
self.backgroundImage = UIImageView(image: UIImage(named: "cafe_interior"))
self.backgroundImage.contentMode = UIViewContentMode.ScaleAspectFill
self.signUpView!.insertSubview(self.backgroundImage, atIndex: 0)
//Log Inボタンの背景色の変更
self.signUpView!.signUpButton?.setBackgroundImage(nil, forState: .Normal)
self.signUpView!.signUpButton?.backgroundColor = ColorDefinition.colorWithHexString("#cc9933")
}
//レイアウト処理が完了した際の処理
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
//ロゴ文言のサイズ設定
self.signUpView!.logo!.sizeToFit()
let logoFrame = self.signUpView!.logo!.frame
signUpView!.logo!.frame = CGRectMake(
logoFrame.origin.x,
self.signUpView!.usernameField!.frame.origin.y - logoFrame.height - 20,
self.signUpView!.frame.width,
logoFrame.height
)
//背景イメージのサイズ設定
self.backgroundImage.frame = CGRectMake(0, 0, self.signUpView!.frame.width, self.signUpView!.frame.height)
}
}
この後のViewController.swiftのカスタマイズに関してはこちらの上記2つのクラスを用いて設定を行っていきます。
※補足:色の細かな設定に関して
今回のサンプルに関しては、私がWebのカラーコードの方が慣れていたので下記のような16進数のカラーコードをUIColorに変換する構造体を作成しておくと色々捗ると思います。
import UIKit
struct ColorDefinition {
//16進数のカラーコードをiOSの設定に変換するメソッド
static func colorWithHexString (hex:String) -> UIColor {
//受け取った値を大文字に変換する
var cString:String = hex.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).uppercaseString
//#があれば取り除く
if (cString.hasPrefix("#")) {
cString = (cString as NSString).substringFromIndex(1)
}
if (cString.characters.count != 6) {
return UIColor.grayColor()
}
let rString = (cString as NSString).substringToIndex(2)
let gString = ((cString as NSString).substringFromIndex(2) as NSString).substringToIndex(2)
let bString = ((cString as NSString).substringFromIndex(4) as NSString).substringToIndex(2)
var r:CUnsignedInt = 0, g:CUnsignedInt = 0, b:CUnsignedInt = 0;
NSScanner(string: rString).scanHexInt(&r)
NSScanner(string: gString).scanHexInt(&g)
NSScanner(string: bString).scanHexInt(&b)
return UIColor(red: CGFloat(r) / 255.0, green: CGFloat(g) / 255.0, blue: CGFloat(b) / 255.0, alpha: CGFloat(1))
}
}
Web開発に馴染みのある方であれば、こちらのようなメソッドを用意しておくととても便利です。
4. ログイン or サインアップ画面の処理をViewController.swiftに記述する
それでは実際にViewControllerの方にログイン or サインアップの処理を記述していきます。

ログイン画面表示の際には先ほど作成したlet login = LoginViewController()
でLoginViewControllerのインスタンスを元に、サインアップ画面表示の際も同様に let signup = SignUpViewController()
でSignUpViewControllerのインスタンスを元に設定をしていきます。
本サンプルでは下記のような仕様となっています。
- Twitter / Facebookのソーシャルアカウントと連携したログインの機能は今回は省略しています。
- サインアップ時の入力項目(Username / Password / Email)に関しては独自バリデーションを設けています。
--- (省略) ---
//PFLogInViewControllerDelegate, PFSignUpViewControllerDelegateの追記を行います。
class ViewController: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
--- (省略) ---
//ParseUIのログイン機能を呼び出す実装を行う
func loginParseUiContents() {
//※UIをカスタマイズするのは面倒臭いときはこれを活用するのも一つの手かも...
//Login画面に表示する要素をカスタマイズする(ParseUI.frameworkで提供されているものを継承したクラスを使用)
let login = LoginViewController()
login.delegate = self
//ログイン画面のフィールド設定
login.fields = (
[
PFLogInFields.UsernameAndPassword, //ユーザー名とパスワードのフィールド
PFLogInFields.LogInButton, //ログイン処理ボタン
PFLogInFields.SignUpButton, //サインアップ画面遷移ボタン
PFLogInFields.PasswordForgotten, //パスワードリマインダー
PFLogInFields.DismissButton //戻るボタン
]
)
//SignUp画面に表示する要素をカスタマイズする(ParseUI.frameworkで提供されているものを継承したクラスを使用)
let signup = SignUpViewController()
signup.delegate = self
//サインアップ画面のフィールド設定
signup.fields = (
[
PFSignUpFields.UsernameAndPassword, //ユーザー名とパスワード
PFSignUpFields.SignUpButton, //サインアップ処理ボタン
PFSignUpFields.Email, //Eメールのフィールド
PFSignUpFields.DismissButton //戻るボタン
]
)
//ログイン画面のサインアップ画面にフィールドを追加する
login.signUpController = signup
self.presentViewController(login, animated: true, completion: nil)
}
/**
* PFLogInViewControllerDelegateのメソッド
*
* ※ ParseUIの機能を利用する
*/
//logInViewController: ログイン処理
func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool {
//ユーザ名とパスワードのチェック(実際はもう少し厳密にすること)
if !username.isEmpty && !password.isEmpty {
return true
} else {
//エラー時にはポップアップを表示する
let alertController = UIAlertController(title: "ログインできません",
message: "ユーザー名またはパスワードが空です。", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
logInController.presentViewController(alertController, animated: true, completion: nil)
return false
}
}
//logInViewController: ログイン処理成功時
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) {
self.dismissViewControllerAnimated(true, completion: nil)
}
//logInViewController: ログイン処理失敗時
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) {
//Debug.
print("Failed to Login Action...")
}
//logInViewController: ログイン処理キャンセル時
func logInViewControllerDidCancelLogIn(logInController: PFLogInViewController) {
//Debug.
print("Cancel to Login Action...")
}
/**
* PFSignUpViewControllerDelegateのメソッド
*
* ※ ParseUIの機能を利用する
*/
//signUpViewController: サインアップ処理
func signUpViewController(signUpController: PFSignUpViewController,
shouldBeginSignUp info: [String : String]) -> Bool {
//初回パスワードに関するバリデーション
if info != [:] {
//サインアップに必要な情報
let username = info["username"]
let password = info["password"]
let email = info["email"]
//ユーザー名は3文字以上でお願いします
if username!.utf16.count < 3 {
//エラー時にはポップアップを表示する
let alertController = UIAlertController(title: "会員登録できません",
message: "ユーザー名は3文字以上でお願いします。", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
signUpController.presentViewController(alertController, animated: true, completion: nil)
return false
//パスワードは8文字以上でお願いします
} else if password!.utf16.count < 8 {
//エラー時にはポップアップを表示する
let alertController = UIAlertController(title: "会員登録できません",
message: "パスワードは8文字以上でお願いします。", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
signUpController.presentViewController(alertController, animated: true, completion: nil)
return false
//Eメールの形式チェック
} else if isValidEmail(email!) == false {
//エラー時にはポップアップを表示する
let alertController = UIAlertController(title: "会員登録できません",
message: "正しいEメール形式を入力して下さい。", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
signUpController.presentViewController(alertController, animated: true, completion: nil)
return false
//正常処理
} else {
return true
}
} else {
return false
}
}
//signUpViewController: サインアップ処理成功時
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) -> Void {
self.dismissViewControllerAnimated(true, completion: nil)
}
//signUpViewController: サインアップ処理失敗時
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?) {
//Debug.
print("Failed to SignUp Action...")
}
//signUpViewController: サインアップ処理キャンセル時
func signUpViewControllerDidCancelSignUp(signUpController: PFSignUpViewController) -> Void {
//Debug.
print("Cancel to SignUp Action...")
self.dismissViewControllerAnimated(true, completion: nil)
}
//Eメールのバリデーションチェック
private func isValidEmail(string: String) -> Bool {
let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx)
let result = emailTest.evaluateWithObject(string)
return result
}
--- (省略) ---
}
おおまかな実装の流れとしましては、PFLogInViewControllerDelegate, PFSignUpViewControllerDelegateを用いて、
- 入力フィールドの作成
- 成功 / 失敗 / キャンセル時の処理
の2つを行っています。上記に関する処理をよしなに記述して残りの部分はParse.comの機能にお任せするというスタイルにはなりますが結構簡単に認証機能を作成することができました。
ただこのように既存のフレームワークの機能を利用している部分はParse.comの更新情報やドキュメントには目を通しておかないといけませんね。
5. ログイン or サインアップ成功時の画面と成功時の確認
それではViewControllerを作り終わったら、実際にログインないしはサインアップを行ってみましょう。
Parse.comにログインをして、下記のスクリーンショットのように
- Userクラスのテーブル
- Sessionクラスのテーブル
に新規に自分のデータが1件追加されていることを確認してみて下さい。
今回のところはここまでとします。
おわりに:次回の予告
今回でParse.comのSDKが提供していうParseUIを使用しての認証ができました。こちらのサンプルはまだまだカスタマイズやチューニングの余地があるかと思いますので私自身ももっとドキュメントやソースを読み込んで深堀りしていきたい所存です。
次回は、
- Chapter3: 登録データをUITableViewに一覧&詳細表示と追加・変更・削除に関する処理
をする予定です。
※順番的にはとびとびになってゆるい感じになってしまうかもしれませんが何かしらの参考になれば幸いです。
追記とその他
Githubはこちら: PremiumCafeList
2015.12.30:
- Chaper2までのサンプルを更新しました。Chapter毎のサンプルを確認する場合は以下のコマンドで確認をしてみて下さい。
$ git clone git@github.com:fumiyasac/PremiumCafeList.git
$ git checkout 50ba87b943a3f7bdfa9a8f84f74857faec6983f8
このサンプルについて
- GithubへのPull Requestならびに要望や改善に関する提案も受け付けていますのでお気軽にどうぞ!