出会いアプリ

今回は出会いをサポートするアプリを開発していきます。
Facebookログインでユーザ登録をし、好みの相手を見つけます。Tinder風のUIで相手を選ぶ事ができ、好みにした相手にメールでメッセージを送る機能を実装していきます。

Xcodeプロジェクト作成

DeaiでXcodeプロジェクトを新規作成してください。
使用するテンプレートはSingle View Applicationとなります。

入力項目 入力値
Product Name Deai
Team None
Oganization Name 任意(”ALJ”など自分が所属している組織の名前を入れる)
Oganization Identifier jp.co.al-j
Language Swift
Devices iPhone
Use Core Data チェックを外す
include Unit Tests チェックを外す
include UI Tests チェックを外す

Cocoapods設定

プロジェクトフォルダをターミナルにドラッグします。

ターミナルの画面でpod initを実行します。

pod init

これでPodfileがプロジェクト内に作成されますので、openコマンドで開きましょう。

open Podfile

テキストエディタが開いてPodfileを編集できますので、下記内容になるように修正し、保存してください。

Podfile

platform :ios, '9.0'
use_frameworks!
target 'Deai' do
pod 'NCMB', :git => 'https://github.com/NIFTYCloud-mbaas/ncmb_ios.git'
end

ターミナルに戻り下記コマンドでNCMBのライブラリをダウンロードします。

pod install

このように表示されればOKです。

一度Xcodeプロジェクトを閉じて、下図のとおりxcworkspaceからXcodeを開き直してください。

NCMB初期設定

次に、NCMBの初期設定をおこないます。

NCMBはObjective-Cという言語で作られており、Swiftで利用する為には、Objective-CからSwiftへ橋渡しする為のファイル(Bridging-Header.h)を作成し、Swiftで扱えるように設定する必要があります。

まずは橋渡しする為のファイル(Bridging-Header.h)を作成しましょう。

プロジェクトの「Deai」フォルダーで右クリックをし、「New Files..」を選択します。

「Header File」を選択し、「Next」を押します。

Save Asに「Deai-Bridging-Header.h」と入力して「Create」ボタンを押します。

「Deai-Bridging-Header.h」を以下へ編集します。

Deai-Bridging-Header.h

#import <NCMB/NCMB.h>

Xcodeへ「Deai-Bridging-Header.h」を読み込ませる為の設定をします。

「Deai」プロジェクトの「Build Settings」の「Objective-C Bridging Header」に「Deai/Deai-Bridging-Header.h」を指定します。

Facebookログイン実装準備

次にFacebookログイン機能を実装するための準備をします。Facebookログインの機能を利用するためにはFacebookが提供しているFacebook Developersサイトでアプリ登録をし、SDKをダウンロードしてプロジェクトに取り込む必要があります。

Facebook Developersサイトにアクセスしてください。

https://developers.facebook.com/

Facebookにログインしていない場合にはログインし、下図のように新しいアプリを追加をクリックします。

表示名、連絡先メールアドレス、カテゴリを選択し、「アプリIDを作成してください」を選択します。

※表示名はios-deai、カテゴリはコミュニケーションを選択します。メールアドレスはご自身のメールアドレスを入力します。

セキュリティチェックで表示されている文字列を入力して「送信」を押します。

次にDownload SDKをダウンロードします。以下のドキュメントボタンをクリックします。

IOS SDKを選択します。

Download the SDKを選択し、ダウンロードします。

ダウンロードが完了したらzipファイルを解凍し、下図のように下記のframeworkをXcodeプロジェクトのFrameworksグループの中にドラッグしてください。

  • Bolts.framework
  • FacebookSDKStrings.framework
  • FBAudienceNetwork.framework
  • FBSDKCoreKit.framework
  • FBSDKLoginKit.framework
  • FBSDKMessengerShareKit.framework
  • FBSDKShareKit.framework

Facebook DevelopersBundle Identifierを設定します。

XcodeのBundle Identifierをコピーします。

Facebook Developersサイトの設定画面を開きます。そしてプラットフォームを追加を押します。

iOSを選択し、バンドルIDを設定します。そして、変更を保存を押します。

次に、Xcodeプロジェクトのinfo.plistファイルを編集していきます。
下図のようにinfo.plistで右クリックし、Open As -> Source Codeをクリックします。
そうすると、info.plistの中身であるXML形式のファイルがエディタエリアに表示されます。

info.plistへ以下を追加します

    <key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<!-- 追加(ここから) -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>fb{your-app-id}</string>
</array>
</dict>
</array>
<key>FacebookAppID</key>
<string>{your-app-id}</string>
<key>FacebookDisplayName</key>
<string>ios-deai</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>fbapi</string>
<string>fb-messenger-api</string>
<string>fbauth2</string>
<string>fbshareextension</string>
</array>
<key>NSPhotoLibraryUsageDescription</key>
<string>PhotoLibrary</string>
<!-- 追加(ここまで) -->
</dict>
</plist>

※「{your-app-id}」はアプリIDを設定してください。

アプリレビューのメニューを選択し、以下の箇所のスイッチをクリックしてONにします。

「アプリを公開しますか?」というダイアログが表示されますので確認ボタンをクリックします。

これでFacebook Developersのアプリ登録設定は完了です。

NCMB利用準備

今回もNCMB(Nifty Mobile Backend)を利用していきます。
Deaiでアプリ登録しておいてください。

http://mb.cloud.nifty.com/

表示されたアプリケーションキークライアントキーは保存しておいてください。

FacebookSDKとNCMBを利用するためのコードを追加していきます。
まずはAppDelegateを開き下記のようにコードを追加してください。

AppDelegate.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
import FBSDKCoreKit
//////////////// ▲▲ 追加 ▲▲ ////////////////
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//////////////// ▼▼ 追加 ▼▼ ////////////////
// NCMB
NCMB.setApplicationKey("【アプリケーションキー】", clientKey: "【クライアントキー】")
// Facebook SDK
FBSDKApplicationDelegate.sharedInstance().application(application, didFinishLaunchingWithOptions: launchOptions)
//////////////// ▲▲ 追加 ▲▲ ////////////////
return true
}
:
:
:
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
//////////////// ▼▼ 追加 ▼▼ ////////////////
FBSDKAppEvents.activateApp()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
return FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

このコードを追加する事によりFacebookSDKとNCMBを使う準備が整いました。

画面レイアウト

設定画面

Main.storyboardで画面レイアウトを構築していきます。

まずはNavigationControllerを追加します。

メニューバーからEditer -> Embed in -> NavigationControllerを選択してください。

このようにNavigationControllerが追加されます。

ViewControllerはImageViewSwitchButtonLabelBar Button Itemを下図のように配置してください。

IBOutletも下図のように関連付けておいてください。

Facebookログイン画面

Facebookログイン画面をレイアウトしていきます。新しくViewControllerを追加し、下図のように青いButtonを配置してFacebookログインというラベルを入力してください。

Segue(Show)で追加したViewControllerと接続し、IdentifiertoLoginにします。

FBLoginViewController.swiftを新しく作成してMain.storyboardに追加したViewControllerと関連付けてください。

FBログイン機能実装

まずViewController.swiftにてNCMBログインチェックをする処理を追加し、未ログインだった場合にはFBLoginViewControllerに遷移するようにします。

ViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////
class SettingViewController: UIViewController {

@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var interestedInWomen: UISwitch!

override func viewDidLoad() {
super.viewDidLoad()
}
//////////////// ▼▼ 追加 ▼▼ ////////////////
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
ncmbLoginCheck()
}
// NCMBログインチェック
func ncmbLoginCheck(){
if (NCMBUser.current() != nil) {
print("ログイン済み")
} else {
print("未ログイン")
self.performSegue(withIdentifier: "toLogin", sender: nil)
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
}

次はFBLoginViewController.swiftにてFBログイン機能を追加します。

FBLoginViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
import FBSDKCoreKit
import FBSDKLoginKit
//////////////// ▲▲ 追加 ▲▲ ////////////////
class ViewController: UIViewController {
:
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
//---------------------------------------
// MARK: - Facebook Login
//---------------------------------------
// ログインボタンタップ時
@IBAction func loginFacebookAction(sender: AnyObject) {
let fbLoginManager : FBSDKLoginManager = FBSDKLoginManager()
fbLoginManager.logIn(withReadPermissions: ["email"], from: self) { (result, error) -> Void in
if (error == nil){
print("FBログイン成功")
// ユーザ取得のパーミッションがある場合にユーザ詳細取得
let fbloginresult : FBSDKLoginManagerLoginResult = result!
if(fbloginresult.grantedPermissions.contains("email")){
self.returnUserData()
}
} else {
print("FBログイン失敗:\(error)")
}
}
}
// ユーザ情報取得
func returnUserData() {
let graphRequest:FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me",
parameters: ["fields": "id,email,gender,link,locale,name,timezone,updated_time,verified,last_name,first_name,middle_name"])
graphRequest.start(completionHandler: { (connection, result, error) -> Void in
if ((error) != nil) {
print("FBユーザ情報取得失敗: \(error)")
}else{
print("FBユーザ情報取得成功: \(result)")
// NCMBユーザ登録
self.ncmbLogin(result: result as AnyObject)
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
}

ボタンタップ時にFacebookログインSDKのFBSDKLoginManagerを利用してFacebookログインを試行します。ユーザがFBのログインを成功させると、ユーザ情報を取得するreturnUserDataメソッドにてFacebookに登録してあるログインしたユーザの情報(メールアドレスや名前、性別)を取得します。

NCMBログイン・ユーザ登録

次にFacebookログインで取得したユーザ情報を使ってNCMBにログイン、またはユーザ登録する処理を追加します。
まずNCMBにログインを試行し、ログイン失敗した場合にはユーザ登録をする流れを追加します。

//////////////// ▼▼ 追加 ▼▼ ////////////////
//---------------------------------------
// MARK: - NCMB Setting
//---------------------------------------
// NCMBログイン
func ncmbLogin(result:AnyObject){
// ユーザーIDとパスワードでログイン
let userId = result.valueForKey("id") as! String
// UserDefaultにパスワードがある場合はそれを利用
let ud = NSUserDefaults.standardUserDefaults()
var password = ""
if let passwd = ud.objectForKey("password") {
password = passwd as! String
} else {
password = NSUUID().UUIDString
ud.setObject(password, forKey: "password")
}
NCMBUser.logInWithUsernameInBackground(userId, password: password, block:({(user, error) in
if (error != nil){
print("ログイン失敗:\(error)")
// 失敗したらユーザ未作成なので作成
self.ncmbUserRegister(result,password: password)
}else{
print("ログイン成功:\(user)")
// 次の画面へ遷移
self.navigationController?.popToRootViewControllerAnimated(true)
}
}))
}
// NCMBユーザ登録
func ncmbUserRegister(result:AnyObject,password:String) {
let user = NCMBUser()
user.userName = result.valueForKey("id") as! String
user.password = password
user.mailAddress = result.valueForKey("email") as! String
user.setObject(result.valueForKey("gender") as! String, forKey: "gender")
user.setObject(result.valueForKey("name") as! String, forKey: "fullname")
// ACL設定(全員を読み込み可能)
let acl = NCMBACL()
acl.setPublicReadAccess(true)
acl.setPublicWriteAccess(true)
user.ACL = acl
// 登録処理
user.signUpInBackgroundWithBlock({(error) in
if (error != nil){
print("ユーザ登録失敗:\(error)")
}else{
print("ユーザ登録完了:\(user)")
// 次の画面へ遷移
self.navigationController?.popToRootViewControllerAnimated(true)
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

その後にNCMBに既に登録されているユーザかのチェックを実施し、未ログインの場合にはユーザ登録の処理ncmbUserRegisterメソッドを呼び出してNCMBにユーザ登録しています。

ユーザプロフィール画像表示・利用設定

FBログインでユーザ情報を取得できましたので、それを使って画面にユーザのプロフィール画像を表示させます。またスイッチで出会いたい性別を選択してそれをNCMBに保存する処理も追加します。

ViewController.swift

// NCMBログインチェック
func ncmbLoginCheck(){
if (NCMBUser.currentUser() != nil) {
print("ログイン済み")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// 自分写真を表示
self.setMyPicture()
//////////////// ▲▲ 追加 ▲▲ ////////////////
} else {
print("未ログイン")
self.performSegueWithIdentifier("toLogin", sender: nil)
}
}
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
// 自分の写真を表示
func setMyPicture(){
let user = NCMBUser.currentUser()
print("user:\(user)")
let fbPictureUrl = "https://graph.facebook.com/" + user.userName + "/picture?type=large"
if let fbpicUrl = NSURL(string: fbPictureUrl) {
if let data = NSData(contentsOfURL: fbpicUrl) {
self.imageView.image = UIImage(data: data)
}
}
}
// ログアウトボタンタップ時
@IBAction func logoutButtonTapped(sender: AnyObject) {
NCMBUser.logOut()
ncmbLoginCheck()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

Main.storyboardを開いてFacebookログインボタンのIBActionを関連付けします。

この状態で一度Runして動作確認してみましょう。Facebookログイン画面が表示してログインすると、ViewController画面に自分のプロフィール画像が表示されていればOKです。

デバッグエリアにもFacebookログイン成功時にユーザ情報が取得できている事を確認してください。

またNCMB管理画面へ遷移し、ユーザ登録されている事も確認してください。

テストユーザ作成

今回は出会いアプリなので、異性のユーザを予め登録しないといけません。
NCMB管理画面でテストユーザを作成しておきます。

下図のようにNCMB管理画面で新しい会員ボタンをクリックするとレコードが1行追加されます。

userNamepasswordを設定するとユーザが追加されます。

user1user5まで作成してください。

また性別は今回は異性は女性にしますのでgenderの項目はfemaleにしてください。またmainAddressfullnameも下図のように入れてください。

最後に作成したユーザのパーミッションを変更します。
下記の部分をクリックしてパーミッションを全員読み込み可、書き込み可にしてください。

TinderUI画面作成

次に下図のように異性のプロフィール画像が表示され、Tinder風に画面をフリックすると、好み、好みじゃないのを選別できるようにします。

画面レイアウト

Main.storyboardを開いて下図のように新しくViewControllerを追加してください。

Main.storyboardを開き新しくViewControllerを追加して、設定画面とSegue(Show)で接続します。identifierはtoTinderにしてください。

TinderViewController.swiftのファイルを作成して関連付けしてください。

Navigation itemを追加してタイトルをお相手確認としておきます。

下図のようにImageViewLabelBar Button Itemを配置して画面レイアウトしてください。

IBOutLetIBActionで関連付けしてください。

好みの性別を選択

次に、好みの性別を選択してNCMBのログインユーザの情報を更新する処理を追加します。

ViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
//-----------------------------------
// MARK: - IB Action
//-----------------------------------
// スタートボタンタップ時
@IBAction func startButtonTapped(sender: AnyObject) {
// NCMBに好みの性別情報を保存
let user = NCMBUser.currentUser()
user.setObject(interestedInWomen.on, forKey: "interestedInWomen")
user.saveEventually({(NSError error) in
if (error != nil) {
print("保存失敗:\(error)")
}else{
print("保存成功:\(user)")
// 次の画面
self.performSegueWithIdentifier("toTinder", sender: nil)
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

Main.storyboardを開いて下図のとおりIBActionを関連付けします。

TinderUI実装

それではTinderUIを実装していきます。
まず画面表示時の処理を追加します。次の機能を追加していきます。
この画面ではログインユーザの現在地を取得し、NCMBのユーザ情報として保存します。
その現在地から近いユーザを検索してそのユーザのプロフィール画像を画面に表示させます。

画面表示時

  • ドラッグ操作検知設定
  • 現在地を取得してNCMBに保存
  • ユーザ取得(探している性別で、現在地から近いユーザを1件取得)
  • プロフィール画像を表示

ドラッグ検知設定と、現在地を取得してNCMBに保存する処理を追加します。

TinderViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import CoreLocation
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////
class TinderViewController: UIViewController {
@IBOutlet weak var userImageView: UIImageView!
//////////////// ▼▼ 追加 ▼▼ ////////////////
let locationManager = CLLocationManager()
var displayedUser = NCMBUser() // 表示しているユーザのID
//////////////// ▲▲ 追加 ▲▲ ////////////////

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
// 画像にドラッグ認識
let gesture = UIPanGestureRecognizer(target: self, action: Selector("wasDragged:"))
userImageView.addGestureRecognizer(gesture)
userImageView.userInteractionEnabled = true
// 現在値取得
self.getCurrentLocationAndDbSave()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
:
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
//--------------------------------------
// 現在地を取得してDB保存
//--------------------------------------
func getCurrentLocationAndDbSave(){
// 位置情報取得
// 承認されていない場合はここで認証ダイアログを表示
let status = CLLocationManager.authorizationStatus()
if(status == CLAuthorizationStatus.NotDetermined) {
print("didChangeAuthorizationStatus:\(status)");
self.locationManager.requestAlwaysAuthorization()
}
NCMBGeoPoint.geoPointForCurrentLocationInBackground({(geopoint,error) in
if error != nil{
print("geo point error:\(error)")
} else {
print("geo point success lat:\(geopoint.latitude), lon:\(geopoint.longitude)")
let user = NCMBUser.currentUser()
user.setObject(geopoint, forKey: "geopoint")
user.saveEventually({(error) in
if error != nil {
print("現在地保存失敗:\(error)")
} else {
print("現在地保存成功:\(user)")
// ユーザ取得
self.fetchUser()
}
})
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
}

現在地を取得するためにはinfo.plistファイルに下記の内容を追加する必要があります。
下図のようにinfo.plistで右クリックし、Open As -> Source Codeをクリックします。
そうすると、info.plistの中身であるXML形式のファイルがエディタエリアに表示されます。

XML形式の中身が表示されたら下記内容を追加してください。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
:
:
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
//////////////// ▼▼ 追加 ▼▼ ////////////////
<key>NSLocationAlwaysUsageDescription</key>
<string>I always need location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>I need location when app in use</string>
//////////////// ▲▲ 追加 ▲▲ ////////////////
</dict>
</plist>

またプロジェクトエディタのCapabilitiesBackground ModesOnにしLocation updatesのチェックボックスを有効にしてください。

この状態で一度実機でRunして動作確認しておいきましょう。
問題なければデバッグエリアに緯度・経度の値が表示されます。
緯度・経度を取得するには実機での確認が必要になります。

一度Runしてみましょう。下図のようにデバッグエリアに現在地保存成功が表示されればOKです。

現在地の緯度・経度の情報がNCMBのユーザに追加されている事も確認しましょう。

今後ユーザ検索の際に他のユーザにも緯度・経度の情報を入れていないといけないので、先程追加したユーザにも緯度・経度の情報をいれておいてください。
(コピー・アンド・ペーストで問題ありません。)

ユーザ取得

それでは好みの相手を探すためのユーザ取得の処理を追加していきます。

  • ユーザ取得(探している性別で、現在地から近いユーザを1件取得)
  • プロフィール画像を表示
//////////////// ▼▼ 追加 ▼▼ ////////////////
//--------------------------------------
// 現在地を取得してDB保存
//--------------------------------------
func getCurrentLocationAndDbSave(){
:
:
:
NCMBGeoPoint.geoPointForCurrentLocationInBackground({(geopoint,error) in
if error != nil{
print("geo point error:\(error)")
} else {
print("geo point success lat:\(geopoint.latitude), lon:\(geopoint.longitude)")
let user = NCMBUser.currentUser()
user.setObject(geopoint, forKey: "geopoint")
user.saveEventually({(error) in
if error != nil {
print("現在地保存失敗:\(error)")
} else {
print("現在地保存成功:\(user)")
//////////////// ▼▼ 追加 ▼▼ ////////////////
// ユーザ取得
self.fetchUser()
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
})
}
})
}

//--------------------------------------
// ユーザ取得
//--------------------------------------
func fetchUser(){
//--------------------------------------
// 現在地に近いユーザ検索
//--------------------------------------
// ログインユーザの現在地取得
let currentGeoPoint = NCMBUser.currentUser().objectForKey("geopoint") as! NCMBGeoPoint
// 探している性別
let interestedInWomen = NCMBUser.currentUser().objectForKey("interestedInWomen") as! Bool
// クエリ作成
let query = NCMBQuery(className: "user")
query.whereKey("geopoint", nearGeoPoint:currentGeoPoint, withinKilometers: 1.0)
// 探している性別に合わせ検索条件変更
if interestedInWomen == true {
query.whereKey("gender", equalTo: "female")
} else {
query.whereKey("gender", equalTo: "male")
}
// 取得件数は1件
query.limit = 1
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error != nil){
print("友達取得失敗:\(error)")
} else {
print("友達取得成功:\(objects)")
if objects.count > 0 {
self.displayedUser = objects[0] as! NCMBUser
// ユーザプロフィール画像表示
let fbPictureUrl = "https://dl.dropboxusercontent.com/u/6866756/swift/deai/" + objects[0].userName + ".png"
// 本番環境は下記
// let fbPictureUrl = "https://graph.facebook.com/" + objects[0].userName + "/picture?type=large"
if let fbpicUrl = NSURL(string: fbPictureUrl) {
if let data = NSData(contentsOfURL: fbpicUrl) {
self.userImageView.image = UIImage(data: data)
}
}
} else {
// 画像を初期化して非表示
self.userImageView.image = UIImage()
self.userImageView.userInteractionEnabled = false
}
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

この状態で一度Runして確認してみましょう。他のユーザのプロフィール画像が表示されればOKです。

ドラッグしたら画像が移動

次に、表示されたプロフィール画像をドラッグしたらドラッグした場所に追随して移動する処理を追加します。

TinderViewController.swift

//--------------------------------------
// ドラッグ検知
//--------------------------------------
func wasDragged(gesture: UIPanGestureRecognizer) {
// ドラッグ度合取得
let translation = gesture.translationInView(self.view)
// ドラッグ対象View取得
let imageView = gesture.view!
// Viewを移動
imageView.center = CGPoint(
x: self.view.bounds.width / 2 + translation.x,
y: self.view.bounds.height / 2 + translation.y
)
// 中央からのX軸移動量取得
let xFromCenter = imageView.center.x - self.view.bounds.width / 2
// X軸移動量から縮小率算出
let scale = min(100 / abs(xFromCenter), 1)
// X軸移動量から回転率算出
var rotation = CGAffineTransformMakeRotation(xFromCenter / 200)
// 回転・縮小の値をセット
var stretch = CGAffineTransformScale(rotation, scale, scale)
// Viewを回転・縮小
imageView.transform = stretch
// ドラッグ完了時X軸が100p以上移動していたらDB登録して次のユーザ表示
if gesture.state == UIGestureRecognizerState.Ended {
var acceptedOrRejected = ""
if imageView.center.x < 100 {
acceptedOrRejected = "rejected"
} else if imageView.center.x > self.view.bounds.width - 100 {
acceptedOrRejected = "accepted"
}
print("好みの相手判断:\(acceptedOrRejected)")
// 元の位置に戻す
rotation = CGAffineTransformMakeRotation(0)
stretch = CGAffineTransformScale(rotation, 1, 1)
imageView.transform = stretch
imageView.center = CGPoint(x: self.view.bounds.width / 2, y: self.view.bounds.height / 2)
}
}

それではこれで一度Runしてみましょう。
下図のように登録したユーザのプロフィール画像をドラッグして移動するか確認してください。

また、ある一定ドラッグして移動するとデバッグエリアにacceptedrejectedが出力される事を確認してください。

好み判断処理実装

acceptedrejectedかを判断し、その情報をNCMBに保存する処理を追加します。そして、一度「好み」か「好みじゃない」かを判断したユーザは検索対象から外して新しいユーザが検索されるようにします。

TinderViewController.swift

//--------------------------------------
// ドラッグ検知
//--------------------------------------
func wasDragged(gesture: UIPanGestureRecognizer) {
:
:
:
// ドラッグ完了時X軸が100p以上移動していたらDB登録して次のユーザ表示
if gesture.state == UIGestureRecognizerState.Ended {
var acceptedOrRejected = ""
if imageView.center.x < 100 {
acceptedOrRejected = "rejected"
} else if imageView.center.x > self.view.bounds.width - 100 {
acceptedOrRejected = "accepted"
}
print("好みの相手判断:\(acceptedOrRejected)")
//////////////// ▼▼ 追加 ▼▼ ////////////////
if acceptedOrRejected != "" {
// 登録処理
let action = NCMBObject(className: "Action")
action.setObject(NCMBUser.currentUser(), forKey: "from")
action.setObject(self.displayedUser, forKey: "to")
action.setObject(acceptedOrRejected, forKey: "acceptedOrRejected")
let acl = NCMBACL()
acl.setPublicReadAccess(true)
action.ACL = acl
action.saveInBackgroundWithBlock({(error) in
if error != nil{
print("Actionテーブル保存失敗:\(error)")
} else {
print("Actionテーブル保存成功:\(action)")
self.fetchUser()
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
// 元の位置に戻す
rotation = CGAffineTransformMakeRotation(0)
stretch = CGAffineTransformScale(rotation, 1, 1)
imageView.transform = stretch
imageView.center = CGPoint(x: self.view.bounds.width / 2, y: self.view.bounds.height / 2)
}
}

acceptedrejectedかを判断したタイミングでNCMBのActionテーブルに保存するようにして、再度ユーザ取得処理を実行するようにしています。

この状態でRunして動作確認してみましょう。ユーザ画像を右か左かにドラッグすると、デバッグエリアに下記の内容が出力されるか確認してください。

また、NCMB管理画面にアクセスしてActionテーブルにデータが追加されている事を確認しましょう。

ユーザ取得処理をこのまま実行すると何回やっても同じユーザしか表示しませんので、一度判断したユーザは除外するようにします。ユーザ取得処理を下記に差し替えてください。

TinderViewController.swift

//////////////// ▼▼ 差し替え ▼▼ ////////////////
//--------------------------------------
// ユーザ取得
//--------------------------------------
func fetchUser(){
//--------------------------------------
// クエリ1.既にアクションしているリストを取得
//--------------------------------------
let query1 = NCMBQuery(className: "Action")
query1.whereKey("from", equalTo: NCMBUser.currentUser())
query1.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error != nil){
print("アクション取得失敗:\(error)")
} else {
print("アクション取得成功:\(objects)")
//--------------------------------------
// クエリ2.現在地に近いユーザ検索
//--------------------------------------
// 現在地取得
let currentGeoPoint = NCMBUser.currentUser().objectForKey("geopoint") as! NCMBGeoPoint
print("currentGeoPoint lat:\(currentGeoPoint.latitude), lon:\(currentGeoPoint.longitude)")
// 探している性別
let interestedInWomen = NCMBUser.currentUser().objectForKey("interestedInWomen") as! Bool
// クエリ作成
let query2 = NCMBQuery(className: "user")
query2.whereKey("geopoint", nearGeoPoint:currentGeoPoint, withinKilometers: 1.0)
// 探している性別に合わせ検索条件変更
if interestedInWomen == true {
query2.whereKey("gender", equalTo: "female")
} else {
query2.whereKey("gender", equalTo: "male")
}
// 取得件数は1件
query2.limit = 1
// アクションした人は対象外
var ignoredUsers = [""]
for object in objects {
let action = object as! NCMBObject
let user = action.objectForKey("to")
print("to user:\(user)")
ignoredUsers.append(user.objectId as String)
}
print("ignoredUsers:\(ignoredUsers)")
query2.whereKey("objectId", notContainedInArray: ignoredUsers)
query2.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if (error != nil){
print("友達取得失敗:\(error)")
} else {
print("友達取得成功:\(objects)")
if objects.count > 0 {
self.displayedUser = objects[0] as! NCMBUser
let fbPictureUrl = "https://dl.dropboxusercontent.com/u/6866756/swift/deai/" + objects[0].userName + ".png"
// 本番環境は下記
// let fbPictureUrl = "https://graph.facebook.com/" + objects[0].userName + "/picture?type=large"
if let fbpicUrl = NSURL(string: fbPictureUrl) {
if let data = NSData(contentsOfURL: fbpicUrl) {
self.userImageView.image = UIImage(data: data)
}
}
} else {
// 画像を初期化
self.userImageView.image = UIImage()
self.userImageView.userInteractionEnabled = false
}
}
})
}
})
}
//////////////// ▲▲ 差し替え ▲▲ ////////////////

上記コードで下記の2つのクエリを実行し、好み判断したユーザを除外して検索するようにしています。

  • クエリ1 Actionテーブルから既にアクションしているリストを取得
  • クエリ2 現在地に近いユーザ検索(クエリ1で取得したユーザは除外)

それではこれで一度Runしてみましょう。
下図のように登録したユーザのプロフィール画像をドラッグしたら、プロフィール画像が切り替わる事を確認してください。

好みユーザ一覧画面作成

「好み」か「好みじゃない」ユーザを判断した後に「好み」のユーザに連絡できるように、好みのユーザを一覧で表示する画面を作成します。

画面レイアウト

Main.storyboardを開き新しくViewControllerを追加して、お相手確認画面とSegue(Show)で接続します。下図の通りBar Button ItemからSegueを追加してください。

ContactViewController.swiftのファイルを作成して関連付けしてください。

Navigation itemを追加してタイトルをお好み一覧としておきます。

下図のようにTableViewを配置してPrototype Cell1にしてください。

CellのIdentifierCellにしてください。

IBOutLetで関連付けしてください。

好みユーザ一覧表示

それでは好みユーザリストをTableViewに表示させるようにします。
好みユーザはNCMBのActionテーブルの中に格納されていて、acceptedOrRejectedacceptedになっているものを探しだします。

ContactViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ プロトコル追加 ▼▼ ////////////////
class ContactViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
//////////////// ▲▲ プロトコル追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
var objects = [NCMBObject]()
//////////////// ▲▲ 追加 ▲▲ ////////////////
@IBOutlet weak var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
tableView.delegate = self
tableView.dataSource = self
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.fetchUsers()
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
:
:
//////////////// ▼▼ 追加 ▼▼ ////////////////
// お好みユーザ取得
func fetchUsers(){
let query = NCMBQuery(className: "Action")
query.whereKey("from", equalTo: NCMBUser.currentUser())
query.whereKey("acceptedOrRejected", equalTo: "accepted")
query.includeKey("to")
query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in
if error != nil {
print("取得失敗:\(error)")
} else {
print("取得成功:\(objects)")
self.objects = objects as! [NCMBObject]
self.tableView.reloadData()
}
})
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ 追加 ▼▼ ////////////////
//----------------------------
// MARK: - TableView DataSource
//----------------------------
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let toUser = self.objects[indexPath.row].objectForKey("to") as! NCMBUser
cell.textLabel!.text = toUser.userName
return cell
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

この状態で確認してみましょう。好みにしたユーザIDがTableViewに表示されていればOKです。

好みユーザ詳細画面

好みユーザ一覧が表示されるようになったので、次は好みユーザのセルをタップしたときにユーザの詳細を表示し、ユーザのメールアドレス宛てにメールを送信できるようにします。

画面レイアウト

Main.storyboardを開き新しくViewControllerを追加して、お好み一覧画面とSegue(Show)で接続します。SegueのIdentifiertoUserDetailにします。

UserDetailViewController.swiftのファイルを作成して関連付けしてください。

Navigation itemを追加してタイトルをユーザ詳細としておきます。

下図のようにImageViewLabelButtonを追加してください。

IBOutLetIBActionで関連付けしてください。

ユーザ詳細表示

好みユーザの詳細を表示するようにします。

UserDetailViewController.swift

import UIKit
//////////////// ▼▼ 追加 ▼▼ ////////////////
import NCMB
//////////////// ▲▲ 追加 ▲▲ ////////////////
class UserDetailViewController: UIViewController{
@IBOutlet weak var mailLabel: UILabel!
@IBOutlet weak var imageView: UIImageView!
//////////////// ▼▼ 追加 ▼▼ ////////////////
var user = NCMBUser() // 選択されたユーザ
//////////////// ▲▲ 追加 ▲▲ ////////////////

override func viewDidLoad() {
super.viewDidLoad()
//////////////// ▼▼ 追加 ▼▼ ////////////////
// メールアドレス表示
mailLabel.text = user.objectForKey("mailAddress") as? String
// 画像表示
let fbPictureUrl = "https://dl.dropboxusercontent.com/u/6866756/swift/deai/" + user.userName + ".png"
// 本番環境は下記
// let fbPictureUrl = "https://graph.facebook.com/" + user.userName + "/picture?type=large"
if let fbpicUrl = NSURL(string: fbPictureUrl) {
if let data = NSData(contentsOfURL: fbpicUrl) {
self.imageView.image = UIImage(data: data)
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}
:
:
}

また、前の画面ではTableViewのセルタップ時の処理を追加してUserDetailViewControllerに画面遷移するようにします。画面遷移時に選択したユーザ情報を送るようにします。

ContactViewController.swift

//////////////// ▼▼ 追加 ▼▼ ////////////////
//----------------------------
// MARK: - TableView Delegate
//----------------------------
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let toUser = self.objects[indexPath.row].objectForKey("to") as! NCMBUser
self.performSegueWithIdentifier("toUserDetail", sender: toUser)
}
//----------------------------
// MARK: - Navigation
//----------------------------
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "toUserDetail" {
let vc = segue.destinationViewController as! UserDetailViewController
vc.user = sender as! NCMBUser
}
}
//////////////// ▲▲ 追加 ▲▲ ////////////////

一度Runしてみましょう。好み一覧画面からセルタップするとユーザの詳細(プロフィール画像、メールアドレス)が表示されている事を確認してください。

ユーザ連絡機能追加

最後にユーザの連絡先を追加する機能を追加していきます。
ユーザのメールアドレス宛てにメールを遅れるようにします。
メールを送るにはMessageUI.frameworkMFMailComposeViewControllerを使うと簡単に実装できます。

UserDetailViewController.swift

import UIKit
import NCMB
//////////////// ▼▼ 追加 ▼▼ ////////////////
import MessageUI
//////////////// ▲▲ 追加 ▲▲ ////////////////

//////////////// ▼▼ プロトコル追加 ▼▼ ////////////////
class UserDetailViewController: UIViewController,MFMailComposeViewControllerDelegate {
//////////////// ▲▲ プロトコル追加 ▲▲ ////////////////
:
:
:
:
@IBAction func contact(sender: AnyObject) {
//////////////// ▼▼ 追加 ▼▼ ////////////////
//メールを送信できるかチェック
if MFMailComposeViewController.canSendMail()==false {
print("Email Send Failed")
return
}
let mailViewController = MFMailComposeViewController()
let toRecipients = [user.objectForKey("mailAddress") as! String] //Toのアドレス指定
mailViewController.mailComposeDelegate = self
mailViewController.setSubject("「出会いアプリ」メール通知")
mailViewController.setToRecipients(toRecipients) //Toアドレスの表示
mailViewController.setMessageBody("", isHTML: false)
// 画像追加
let fbPictureUrl = "https://graph.facebook.com/" + NCMBUser.currentUser().userName + "/picture?type=large"
if let fbpicUrl = NSURL(string: fbPictureUrl) {
if let data = NSData(contentsOfURL: fbpicUrl) {
mailViewController.addAttachmentData(data, mimeType: "image/png", fileName: "image")
}
}
self.presentViewController(mailViewController, animated: true, completion: nil)
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

//////////////// ▼▼ 追加 ▼▼ ////////////////
// メール完了時
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
switch result {
case MFMailComposeResultCancelled:
print("メール送信キャンセル")
break
case MFMailComposeResultSaved:
print("メールドラフト保存")
break
case MFMailComposeResultSent:
print("メール送信完了")
break
case MFMailComposeResultFailed:
print("メール送信失敗")
break
default:
break
}
// 画面を閉じる
self.dismissViewControllerAnimated(true, completion: nil)
}
//////////////// ▲▲ 追加 ▲▲ ////////////////
}

これでRunして動作確認してみましょう。下図のように「連絡する」ボタンをタップしたらメール送信画面が表示されればOKです。

これでアプリは完成です。お疲れ様でした。