iOS11向けにSocial.frameworkを使わないTwitter投稿

基本的にTwitter公式にある情報です。
この記事を読むよりも公式の情報をちゃんと読んだ方がいいと思います。

■ Migrating from iOS Social Framework
https://dev.twitter.com/twitterkit/ios/migrate-social-framework


以下は日本語が読みたい人向けに。 (※17/6/26の内容を元にしています)

6/27 追記
説明の中で使用されているTwitter Kit v3.0はiOS9+をサポート条件にしています。
そのため、アプリのサポートバージョンをiOS9以降に更新してから利用しましょう。
https://dev.twitter.com/twitterkit/ios/upgrading

## 概要
例年なら9月上旬くらいには公開されるiOS11からはSocial.frameworkを通じてTwitterを利用できなくなります。
その代わりに、TwitterKit3を利用してツイートやユーザーのログイン、TwitterAPIを使用します。

## TwitterKit3
[インストールページ](https://dev.twitter.com/twitterkit/ios/installation)からダウンロードすることもできますが、私はとりあえずCocoaPodsでやります。

```rb:Podfile
pod 'TwitterKit'
```

## アプリケーションの登録
開発者用に利用するTwitterアカウントで電話番号の認証を済ませた上で、
[Twitterアプリのダッシュボード](https://apps.twitter.com/)からアプリケーションを作成します。

1. 読み書き可能なアプリケーションで作成（Access level: Read and write）
2. 互換性のため、コールバックURLを設定。使用するつもりがないなら`https://example.com`でも設定しておけと書いてある。
3. Settingsから`Allow this application to be used to Sign in with Twitter`にチェックを入れる


## TwitterKitの初期化処理を設定
### URLスキームの追加

```xml:info.plist
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>twitterkit-{yourConsumerKey}</string>
    </array>
  </dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
    <string>twitter</string>
    <string>twitterauth</string>
</array>
```

6/29　追記
上記の設定を行なっていても、以下のようなエラーが出力されることがあります。

```
Terminating app due to uncaught exception 'TWTRInvalidInitializationException', 
reason: 'Attempt made to Log in or Like a Tweet without a valid Twitter Kit URL Scheme set up in the app settings. 
Please see https://dev.twitter.com/twitterkit/ios/installation for more info.'
```

原因はTwitterKitの不具合で、CFBundleURLSchemesの先頭に追加しないと判定されないようです。(v3.0.3時点）
https://twittercommunity.com/t/twitterkit-v3-causes-exception-upon-sign-in-on-ios/88143/7

### AppDelegateにハンドラを追加
```swift:appDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
  if Twitter.sharedInstance().application(app, open: url, options: options) {
    return true
  }
  // Your other open URL handlers follow […]
}
```

### Twitter.startを呼ぶ
```swift:appDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  Twitter.sharedInstance().start(withConsumerKey: "example-hPpFPVQ64pThkM0", consumerSecret: "exmaple-ovAqziZzzHpU5F163Qg9mj")
  return true
}
```


## Twitterでログイン

###　ACAccountStore.requestAccessToAccounts -> Twitter.logIn

```swift:ACAccountStore
let account = ACAccountStore()
let accountType = account.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter)

account.requestAccessToAccounts(with: accountType, options: nil, completion: {(success, error) in
  if success {
    if let twitterAccount = account.accounts(with: accountType).last as? ACAccount {
      print("signed in as \(twitterAccount.userame)");
    }
  }
})
```

TwitterKitを利用した場合

```swift:TwitterKit
Twitter.sharedInstance().logIn(completion: { (session, error) in
  if let sess = session {
    print("signed in as \(sess.userName)");
  } else {
    print("error: \(error.localizedDescription)");
  }
}
```

### ACAccountStore.accounts -> TWTRSessionStore
アプリケーションを認証すると、TwitterKitがセッションを保存します。
ログイン後は、`Twitter.sharedInstance().sessionStore.session()`を利用すれば現在の認証済みセッションを取得することができます。
アプリにセッションがあるかどうかは`Twitter.sharedInstance().sessionStore.hasLoggedInUsers()`でテストできます。
https://dev.twitter.com/twitterkit/ios/log-in-with-twitter

## ツイートする
TwitterKitを利用してリンク、画像、ビデオの共有が行えます。

### SLComposeViewController -> TWTRComposer

```swift:SLComposerViewController
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
  let composer = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
  composer.setInitialText("Hello Twitter!")
  composer.addImage(UIImage(named: "hello"))
  composer.addURL(URL(string: "https://dev.twitter.com"))
  self.presentViewController(socialController, animated: true, completion: nil)
}
```

TwitterKitでは`TWTRComposer`で同じ機能を提供します。

```swift:TWTRComposer
let composer = TWTRComposer()
composer.setText("Hello Twitter!")
composer.setImage(UIImage(named: "hello"))
composer.setUrl(URL(string: "https://dev.twitter.com"))
composer.show(from: self, completion: nil)
```

ビデオの投稿なども含めたツイートの構成については、`TWTRComposeViewController`に関するドキュメントを参照
https://dev.twitter.com/twitterkit/ios/compose-tweets


## TwitterAPIを利用する
上記の手順にしたがってユーザーの承認を更新し、OAuthで署名されたリクエストを使います。

### SLRequest -> TWTRAPIClient

```swift:SLRequest
let account = ACAccountStore()
let accountType = account.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter)

account.requestAccessToAccounts(with: accountType, options: nil, completion: {(success, error) in
    if success {
        if let twitterAccount = account.accounts(with: accountType).last as? ACAccount
       let statusesShowEndpoint = "https://api.twitter.com/1.1/statuses/show.json"
          let params = ["id": "20"]

          let request = SLRequest(forServiceType: SLServiceTypeTwitter,
      requestMethod: SLRequestMethod.GET,
      URL: URL(string: statusesShowEndpoint),
      parameters: params)
           request?.account = twitterAccount
       request?.perform(handler: {(responseData, urlResponse, error) in
                if let err = error {
                    print("Error : \(err.localizedDescription)")
                } else {
                    print("The first Tweet: \(responseData)")
                }
            })
        }
    }
})
```

TwitterKit

```swift:TWTRAPIClient
if let twitterAccountId = Twitter.sharedInstance().sessionStore.session()?.userID {
  let client = TWTRAPIClient(userID: twitterAccountId)
  let statusesShowEndpoint = "https://api.twitter.com/1.1/statuses/show.json"
  let params = ["id": "20"]
  var clientError : NSError?

  let request = client.urlRequest(withMethod: "GET", url: statusesShowEndpoint, parameters: params, error: &clientError)

  client.sendTwitterRequest(request) { (response, responseData, error) -> Void in
    if let err = error {
        print("Error: \(err.localizedDescription)")
    } else {
      print("The first Tweet: \(responseData)")
    }
  }
} else {
  // Not logged in
}
```


詳しくはやっぱり公式ドキュメント見た方がいいです。