Facebookシェアのサンプルアプリを作成する-実装編-

「Facebookシェアのサンプルアプリを作成する」の実装編になります。準備編は[こちら](http://qiita.com/akatsuki174/items/8da426ab61eee72f6691)。
この記事ではfacebook for developerにあるクイックスタートに沿って説明します。

# 使用Facebook SDKバージョン

v4.11.0

# 動作確認実施端末

実機のiOS9.2, iOS9.3.1
(コード上は8.0以上で動くようになっているはずですが、確認は行っていません。。。)


# 完成品

## ソースコード

[Github](https://github.com/akatsuki174/SocialShareSample)に上げてあります。この記事に貼っているコードはここにもあります。

## スクショ

初期画面
![IMG_0001.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/2caa1ee8-3d79-b0bf-076c-be5994216cd8.png)
↓シェアボタンタップ時の挙動

|条件|遷移1|遷移2|
|:--|:--|:--|
|端末にFacebookアプリが入っている|![IMG_0332.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/69a03ba0-c536-8845-6e5e-0d0afd8cd24b.png)|![IMG_0333.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/a48fe090-c078-536f-35a1-6fc6303ded59.png)
|
|端末にFacebookアプリが入っていない|![IMG_0002.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/3a5d5c92-aeff-1d2b-1985-ba02e8193965.png)|![IMG_0003.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/0df09e3e-38f9-d578-6d8f-ec602df80a39.png)|
|Facebookアプリはあるがアカウントが登録されていない|![IMG_0004.PNG](https://qiita-image-store.s3.amazonaws.com/0/100736/089f08bb-1ee6-a1cf-7ab6-b5247ec6a2fd.png)||

Facebookシェアという点では条件の一番上の実装さえできていればいいのですが、一応エラーハンドリング的な意味を含めて下2つの実装も含めました。

# プラットフォーム選択

developerページ - ダッシュボードで「プラットフォームを選択」をクリックします。
![スクリーンショット 2016-04-24 14.34.17.png](https://qiita-image-store.s3.amazonaws.com/0/100736/040514c9-5304-86a3-5a8d-1206b436472a.png)
プラットフォームはもちろんiOSを選択します。

# Facebook SDKダウンロード

Facebook SDKをダウンロードします。ダウンロードしたZipを解凍すると中身はこんなかんじになっています。
![スクリーンショット 2016-04-24 16.11.14.png](https://qiita-image-store.s3.amazonaws.com/0/100736/2eb68b48-d178-f0fd-f582-a3c0eb5b15df.png)
ちなみにSamplesのフォルダ内にはShareItというシェアのサンプルアプリ(ShareIt)があります。これを参考にするだけでもシェア機能の実装をすることは十分できますが、このアプリはObjective-Cで書かれていますしログイン機能など他の機能もついているので、この記事ではSwiftかつシェアにだけ焦点を当てたものを作って説明しようと思います。

# プロジェクトにSDKを追加

先ほどダウンロードしたもののうち、必要なFrameworkを自分のプロジェクトに入れます。
公式ページではFBSDKCoreKit.Framework, FBSDKLoginKit.Framework, FBSDKShareKit.Frameworkを入れる、と書いてありますが、今回はシェア機能だけ実装するのでLoginKit以外の2つを入れます。

# info.plist設定

いくつかの設定値を加えます。公式ページに書かれている通りにやればOKです。

# Bundle Identifierの登録

developerページにBundle Identifierを登録します。

# App Delegateにコードを追加

基本的にはObjective-CのコードをSwiftに読み替えて追加すればいいのですが、最後の

```swift
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool
```

というメソッドはoption+クリックで見てみるとこんな説明が出てきます。
![スクリーンショット 2016-04-24 17.15.01.png](https://qiita-image-store.s3.amazonaws.com/0/100736/1d15e79c-5391-2dc8-3a8d-d8007fd803ee.png)
Deprecatedになっています。この問題については[stackoverflow](http://stackoverflow.com/questions/33737403/fbsdkapplicationdelegate-application-openurlsourceapplicationannotation-deprec)にも載っていました。
ただ、UIApplicationOpenURLOptionsSourceApplicationKeyはiOS9以上でしか使えないので、iOS8以下の場合は上記のメソッドをそのまま使って、iOS9以降は下記のメソッドを使うようにしました。

```swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool
{
  let sourceApplication: String? = options[UIApplicationOpenURLOptionsSourceApplicationKey] as? String
  return FBSDKApplicationDelegate.sharedInstance().application(app, openURL: url, sourceApplication: sourceApplication, annotation: nil)
}
```

# シェア実装

## シェアボタンを作る

余談ですが、そして今更ですがSwift2.2でselectorを渡す時の記述方式が変わったんですね。今まで通りに以下のように記述したら

```swift
button.addTarget(self, action: "tappedButton:", forControlEvents: .TouchUpInside)
```
こんな警告が出ました。

`Use of string literal for Objective-C selectors is deprecated; use '#selector' instead`

[こちらの記事](http://qiita.com/WorldDownTown/items/b9d5d0cdabd634290f5b#use-of-string-literal-for-objective-c-selectors-is-deprecated-use-selector-instead)に書かれていますが、selectorは`#selector(<Class>.<MethodName>)`という形で書くようになりました。ので、警告に従って書き直すとこのような形になります。

```swift
button.addTarget(self, action: #selector(ViewController.tappedButton(_:)), forControlEvents: .TouchUpInside)
```

## シェア機能の実装

```swift
func tappedButton(sender: UIButton) {
  let url:NSURL = NSURL(string:"https://www.google.co.jp")!
  let dialog = FBSDKShareDialog()
  dialog.shareContent = self.getShareLinkContent(url)
  dialog.mode = FBSDKShareDialogMode.Native
  if dialog.canShow() {
    FBSDKShareDialog.showFromViewController(self, withContent: self.getShareLinkContent(url), delegate: nil)
  } else {
    self.showNotFoundFacebookAccountAlert()
  }
}
    
private func getShareLinkContent(objectURL: NSURL) -> FBSDKSharingContent {
  let content = FBSDKShareLinkContent()
  content.contentURL = objectURL
  return content
}
```

URLを詰め込んだFBSDKSharingContentをFBSDKShareDialogに渡して、ダイアログを開けるのならシェアダイアログを出すという流れになります。

## 設定アプリへ遷移させる

```swift
private func showNotFoundFacebookAccountAlert() {
  let title = "Facebookアカウントが\nありません"
  let message = "Facebookアカウントが\n登録されていません。\n設定アプリから作成、追加ができます。"
  let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
  alert.addAction(UIAlertAction(title: "キャンセル", style: .Cancel, handler: nil))
  alert.addAction(UIAlertAction(title: "設定する", style: .Default, handler: { action in
    self.moveToFacebookSettings()
  }))
  self.presentViewController(alert, animated: true, completion: nil)
}
    
private func moveToFacebookSettings() {
  dispatch_async(dispatch_get_main_queue(), {
    let url = NSURL(string: "prefs:root=FACEBOOK")
    if UIApplication.sharedApplication().canOpenURL(url!) {
      UIApplication.sharedApplication().openURL(url!)
    }
  })
}
```

dialog.canShow()でfalseが返ってきた時の実装になります。
アラートを表示して、「設定する」をタップされた場合は設定アプリのFacebookページへ飛ばします。設定アプリに飛ばす方法は「[設定アプリの各画面に一発で遷移するURLスキームを使ってSafariの検索エンジン設定画面を開いてみる](http://qiita.com/tochiba/items/2794dd1c9d2523276e61)」などを参考にして実装しました。
URLスキームを使って設定アプリを起動できるのはiOS5.0.1以前とiOS8.0以降なので気をつけてください。

# モード切り替え

今のままだと他のユーザがシェア機能を使うことが出来ない状態なので、開発モードからライブモードに変更しなければなりません。
developerページのアプリレビュータブをクリックしてください。このような表示があるはずです。
![スクリーンショット 2016-04-30 22.23.22.png](https://qiita-image-store.s3.amazonaws.com/0/100736/f7ce84a9-48d7-6b90-a7cd-637f397eac9c.png)
スイッチの部分をクリックすると次の表示が現れます。
![スクリーンショット 2016-04-30 22.27.11.png](https://qiita-image-store.s3.amazonaws.com/0/100736/11a39d8d-ee96-8114-44fe-8cd94a15d2f5.png)
確認をクリックすれば「ライブ」に変更になり、他のユーザも使えるようになります。

これでFacebookシェアを実現することができました！

# 番外編：ログについて

何やら見慣れぬログがコンソールに吐かれています。一つ一つ見ていきたいと思います。

## その1

```text:ログ
-canOpenURL: failed for URL: "fbapi20150629:/" - error: 
"This app is not allowed to query for scheme fbapi20150629"
```
iOS9端末でシェアボタンをタップすると上記のログが吐かれます。このアプリにおいては上記のログが吐かれたところで特に問題なく動くのですが("fbapi"は最初にLSApplicationQueriesSchemesに設定したので、そちらがうまくやってくれているんじゃないかとは思いますが)、ログが出ているのは気になります。
調べてみると[stackoverflow](http://stackoverflow.com/questions/33148544/facebook-share-content-only-shares-url-in-ios-9)では「FBSDKShareDialogのmodeを.Nativeと.FeedBrowserを組み合わせて使えば解消されるよ」と書かれていたのでやってみたのですが解消されず。。。
[公式の説明](https://developers.facebook.com/docs/ios/ios9)を見てみると、SDK v4.5以前のバージョンの時はfbapi20150629をLSApplicationQueriesSchemesに追加しなくてはいけなかった様子。しかし今自分が使っているのはv4.11.0なのでこの設定は必要ないはず。。。

結局今回公開しているアプリでは、LSApplicationQueriesSchemesにfbapi20150629を追加するという方法を取っています。根本的な解決にはなっていないので何かいい方法を見つけたら変えようと思います。

※ちなみに自分がどのSDKバージョンを使っているかはダウンロードしてきたフォルダ名(FacebookSDKs-iOS-XXXXXXXX)と[更新履歴](https://developers.facebook.com/docs/ios/change-log-4.x/)を照らし合わせて地道に割り出しました。

## その2

```text:ログ
-canOpenURL: failed for URL: "fbauth2:/" - error: "(null)"
```

シェアボタンをタップしたものの端末にFacebookアプリがない、という状況の時に上記のログが出てきました。[公式のよくある質問](https://developers.facebook.com/docs/ios/ios9)では以下のように書かれていました。

```
canOpenURL: failed for URL: "fb...://などのコンソールメッセージが表示されるのはどうしてですか。

これは、Xcodeの警告であり、canOpenURL:呼び出しがfalseを返したことを意味します。
上述のplistにLSApplicationQueriesSchemesエントリが構成されているのであれば、
この警告は無視してかまいません。
```

とのことです。なのでこのログは気にしなくても構いません。

## その3

```text:ログ
** -[UIApplication _handleNonLaunchSpecificActions:forScene:withTransitionContext:completion:] **
unhandled action -> <FBSSceneSnapshotAction: 0x13652ebb0> {
    handler = remote;
    info = <BSSettings: 0x136579560> {
        (1) = 5;
    };
}
```

アプリをデバッグ起動→端末の電源ボタンを押してスリープ状態にするという動作を行うと上記のログが出ます。
これに対しては次の情報が見つかりました。
●[mapboxのissue](https://github.com/mapbox/mapbox-gl-native/issues/2172)
→iOSのバグである
●[stackoverflow](http://stackoverflow.com/questions/32658037/ios9-storyboard-what-is-unhandled-action-handlenonlaunchspecificactions)
→アップルが内部的に送っているログメッセージであり、コードに問題があるわけではない
●[Apple Developer Forums](https://forums.developer.apple.com/thread/19533)
→解決策が見つかっておらず

これに関しては先に紹介したFacebookが出しているシェアのサンプルアプリでも同じ現象が起きている状態なので、今回はこのログに関しては無視することにしました。