はじめに
2017/9/20(水)にiOS11が一般公開されました
これにより、一部のユーザーがTwitterに投稿できなくなるという不具合をアナウンスしているのをちらほら見かけます
以下では、その原因と対応方法の一例を紹介します
「TwitterKit対応忘れてた!」という方の参考になれば幸いです
iOS 11で起こる問題
結論から言うと、iOS 11ではTwitterアプリが端末にない場合、Social.frameworkまたはUIActivityViewControllerによるTwitterへのShareができなくなりました
設定アプリの変化
iOS 11で設定アプリからTwitter, Facebookなどの項目が消えました
これにより、Twitterのアカウント情報を端末へ登録できなくなりました
Social.frameworkへの影響
iOS 10まで
Social.frameworkを利用したTwitter Share機能を簡単に実装することができました
func shareWithSocialFramework(text: String, on viewController: UIViewController) {
guard let composeVC = SLComposeViewController(forServiceType: SLServiceTypeTwitter) else {
return
}
composeVC.setInitialText(text)
viewController.present(composeVC, animated: true, completion: nil)
}
iOS 11
上記コードを実行すると以下のような挙動になります
Twitterアプリ無し | Twitterアプリ(v7.7.2) インストール済み |
---|---|
何も表示されない |
Twitterアプリが端末にインストールされている場合はTwitterアプリのShare Extensionで表示されますが、アプリがない場合はうんともすんとも言いません
iOS11から各serviceTypeがdeprecated扱い
Twitterだけではなく、Facebookなども同様です
@available(iOS, introduced: 6.0, deprecated: 11.0)
public let SLServiceTypeTwitter: String
@available(iOS, introduced: 6.0, deprecated: 11.0)
public let SLServiceTypeFacebook: String
...
UIActivityViewControllerは?
Twitterアプリが無い場合はそもそもこの一覧に表示されません🙅
iOS 11ではTwitterKitを使う
これではTwitterアプリが入っていない場合Shareすることができないため、iOS11ではSocial.frameworkの代わりにTwitterKitを用いて実装する必要があります
TwitterKitでは端末でglobalなアカウント情報は持たず、アプリごとに管理する形になります
環境
version | |
---|---|
TwitterKit | v3.1.1 |
Deployment Target | iOS 9.0 ↑ |
※ v3.2.1では投稿画面においてスクロール周りのバグがあります
TwitterKitの導入
基本的なことは公式(Installation)にまとまっています
Social.frameworkからのマイグレーションという観点では公式(Migrating from iOS Social Framework)やこちらの記事にまとまっています
導入手順をざっくりとまとめると以下のとおり
- TwitterKitを直接、もしくはCocoaPods/Carthage経由で導入
- Twitter Application Managementでappを作成
- info.plistにCFBundleURLTypesとLSApplicationQueriesSchemesを追加
-
AppDelegate#application(_:open:options:)
にハンドリング処理を追加 - アプリ起動時に
Twitter.sharedInstance().start(withConsumerKey: key, consumerSecret: secret)
する
3.と4.はアカウント連携時の [自分のアプリ ⇔ Twitterアプリ] 間遷移に必要になります
Callback URLsの設定 (2018/08/06 追記)
Twitter Application Management -> app -> Settings -> Callback URLs に twitterkit-{CONSUMERKEY}://
({CONSUMERKEY}
は導入時に付与されるConsumer key)を設定する必要があります
この設定が漏れると、一部条件下でログインに失敗するようになりました(2018/06/13〜)
詳細 - Callback URLs
1アカウントの連携で良い場合
投稿
基本的に以下の実装だけでTwitterKitがよしなにしてくれます(ログイン周りも含め)
let composer = TWTRComposer()
composer.setText(text)
composer.show(from: viewController, completion: { result in ... })
投稿の様子 (Twitter App有) |
簡単なフロー |
---|---|
session情報は保持されるため、一度認証すればそれ以降は直接投稿画面が表示されます
ログアウト
// sessionを管理するTWTRSessionStoreを取得
let sessionStore = Twitter.sharedInstance().sessionStore
// アクティブなアカウントのsessionを取得
if let session = sessionStore.session() {
// userIDでログアウト
sessionStore.logOutUserID(session.userID)
}
複数アカウントの連携をする場合
ユーザーがその時々によってshareするアカウントを変えたいという欲求に答えるためには複数アカウントへの対応をする必要があります
1アカウントの連携で良い場合に対し、こちらでいくつかの制御を行う必要があります
実装例を元にした躓きポイントとその対処方法
TwitterKitには現状いくつかの躓きポイントがあります(少なくとも自分は躓きました)
以下では、複数アカウントの連携を実現する1つの実装例を元に、対処方法を述べていきます
1. アプリ内で複数アカウントを管理する必要がある
iOS10まで
設定アプリにある以下の画面でアカウントの追加連携と連携解除ができました
iOS11(TwitterKit)
自前でアカウントを管理する必要があります
今回は自前の管理画面を用意しました
Add Accountで追加連携、セル削除で連携解除を行います
[追加連携]
Twitter#logIn(with:completion)
で明示的に連携処理を走らせることができます
[連携解除]
TWTRSessionStore#logOutUserID(_:)
で指定したuserID
に対応するsessionを破棄できます
2. APIを叩いて複数アカウントの情報を一度に取得する
TwitterKitはアカウントを連携しても**userID
しか保存しません**(正確にはauthToken
とauthTokenSecret
も)
name
, screenName
等の情報は自分で取得する必要があります
1アカウントのみの取得であれば TWTRAPIClient#loadUser(userID:completion:)
で取ることができます
ただし、今回はアカウント管理画面表示時に最新のアカウント情報を複数同時に取りたいため、以下のAPIを叩く必要があります
TWTRAPIClient
にはAPIのendpointを指定してリクエストするmethodが用意されています
func urlRequest(withMethod method: String, url URLString: String, parameters: [AnyHashable : Any]?, error: NSErrorPointer) -> URLRequest
func sendTwitterRequest(_ request: URLRequest, completion: @escaping TWTRNetworkCompletion) -> Progress
こちらを利用することで同時に複数ユーザの情報を取得できます
func fetchUsers(withUserIDs userIDs: [String]) {
let client = TWTRAPIClient()
let userIDsString = userIDs.joined(separator: ",")
let params = ["user_id": userIDsString]
var clientError: NSError?
let url = "https://api.twitter.com/1.1/users/lookup.json"
let request = client.urlRequest(withMethod: "GET", url: url, parameters: params, error: &clientError)
client.sendTwitterRequest(request) { (response, data, connectionError) in
// エラーハンドリングとParse処理
}
}
3. ツイートするアカウントを切り替える
複数アカウントを連携するとSocial.frameworkと同様にアカウント切り替えのUIが表示されます
Social.frameworkでは、これを切り替えることでそのアカウントが次回以降も保持されていました
しかし、TwitterKitを用いる場合は最後に連携したアカウントで固定となっています
変更する為の専用methodは存在しなさそうですが、以下のmethodでsessionを上書きすることで変更できます
func save(_ session: TWTRAuthSession, completion: @escaping TWTRSessionStoreSaveCompletion)
save処理が非同期なので、直後にツイート画面を表示する場合はcompletionに処理を書く必要があります
func changeDefaultUser(toUserID userID: String, completion: @escaping (_ success: Bool) -> Void) {
let sessionStore = Twitter.sharedInstance().sessionStore
guard let session = sessionStore.session(forUserID: userID) else {
completion(false)
return
}
sessionStore.save(session) { (_, _) in
completion(true)
}
}
アカウント切り替えの様子 (コードによる切り替え + 手動切り替え) |
---|
全体の流れ
これらの点を踏まえ、以下のような形で実装しました(あくまで一例です)
Sessionが無い時のフローはほぼ「1アカウントの連携で良い場合」と同じになるため省略しています
最低限ツイートできるようにするだけであれば一部UIを削ることもできますが、なるべくユーザーが迷わないような形を取っています
また、アプリ内の設定等にTwitterアカウント管理画面を用意するのも良いと思います
どこに何を置くかは、アプリ構造やTwitter連携をログインに使っているかどうかなどとの兼ね合いで決めるべきかと思います
その他
連携画面のプライバシーポリシーと利用規約
これらのリンクはTwitter Application Managementで以下2つのURLを設定することで有効になります
投稿に失敗するケース
TwitterKit v3.1.1〜 で再現するバグとして、^<>\|`
など、一部文字列を含む投稿ができないという問題があります
Twitter DevelopersのForumsにTopicはあるようですが、こちらはまだ未解決のようです
投稿完了のcompletionについて
TWTRComposer#show(from:completion:)
による投稿が成功した際、投稿画面のmodalが閉じます
ただし、この時completionが呼ばれるタイミングとしてはmodalが完全に閉じ切るより前になります
その為、投稿完了後別のVCをmodalとしてpresentする場合には、delayを付けるなどの対応が必要になります
※ 2年ほど前に同様のTopicが上がっているようですが、未解決のままCloseされてしまっているようです
端末の時間がずれているとエラーが返ってくる
端末の時間がずれた状態でTwitterのAPIを叩くと以下のようなエラーが返り失敗することがあります
{"errors":[{"code":135,"message":"Timestamp out of bounds."}]}
必要に応じてユーザーにその旨を伝えるようエラーハンドリングをすると良いかと思います
サンプルアプリ
iOS11-TwitterShareSample [2017/12/06 更新]
ここまでの内容を元に複数アカウント対応したものをサンプルアプリとしてgithubに上げてあります
正常系の流れを追いやすいよう、エラーハンドリングや細かい処理は省いています
あくまで実装の一例なので、参考程度に見ることをおすすめします
※ TwitterUserデータをキャッシュするためにRealmを使っています
まとめ
1つの実装例をベースにTwitterKitの使い方を紹介してきました
Social.frameworkのときに比べ実装量が一気に増え、必要なら画面を追加する場合もあり、現状維持にしては思ったより対応に工数が取られる印象でした
また、実際にTwitterKitを導入してみたところ、まだまだ不安定で痒いところに手が届かないなと身にしみて感じたので、今後のアップデートで改善されていくことを切に願っています
少し長くなりましたが、TwitterKit導入の参考になれば幸いです