Swifterを自分のアプリに組み込む方法


@k-boyさんの記事を参考にSwifterを自分のアプリに組み込んでみたのですが、色々とつまづいたので備忘のため手順を残しておきます。

使用したバージョン

  • Xcode : 10.3
  • Swifter : 1.7.0

1. 自分のアプリにCocoaPodでSwifterをインストールする

  • 自分のアプリディレクトリに移動し、$ pod initを実行する
  • Podfileが作成されるのでpod 'Swifter', :git => 'https://github.com/mattdonnelly/Swifter.git'を追記する
  • $ pod install を実行する
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'MyTweetTool' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for MyTweetTool
  pod 'Swifter', :git => 'https://github.com/mattdonnelly/Swifter.git'
  pod 'SwiftGifOrigin', '~> 1.7.0'
end

2. Twitter Developersで自分のアプリを登録する

Twitter Developersに自分のアプリを登録します。
細かい手順は割愛しますが@tdknさんの記事が参考になります。

登録が完了し、最終的に下記のように自分のConsumer API keysが確認できればOKです。

3. Custom URL Schemeを設定する

SwifterはOAuthにてAPIの認証を行うため、一時的にブラウザを開きます。
開いた先から自分のアプリに戻って来られるようにCustome URL Schemeを設定します。

  • Info.plistに自分のCFBundleURLTypes定義を追加する
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLName</key>
            <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>swifter-{Consumer API key}</string>
            </array>
        </dict>
    </array>

※CFBundleURLSchemesにswifter-{Consumer API key}とありますが、{Consumer API key}には先程のTwitter DevelopersのAPI keyを入力します

  • AppDelegate.swiftでapplication:openURL:options:をオーバーライドする
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        return Swifter.handleOpenURL(url, callbackURL: URL(string: "swifter-{Consumer API key}://")!)
}

※Swifterのコードを呼び出すのでimport SwifteriOSが必要です

4. Swifterを使ってtweetを投稿する

自分が作成したのはテキスト入力と投稿ボタンだけのシンプルなUIのアプリで画面構成は下記のようになります。

説明よりも実際のコードを紹介します。

ViewController.swift
import UIKit
import SwifteriOS
import SafariServices

class ViewController: UIViewController, SFSafariViewControllerDelegate {

    @IBOutlet weak var tweetMsgArea: UITextView!
    private let appStatus = AppStatus()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    @IBAction func tweetPushed(_ sender: Any) {
        tweet()
    }

    func tweet() {
        let TWITTER_CONSUMER_KEY = "{Consumer API key}"
        let TWITTER_CONSUMER_SECRET = "{Consumer API secret key}"

        // load from UserDefaults
        let tokenKey = self.appStatus.twitterTokenKey
        let tokenSecret = self.appStatus.twitterTokenSecret

        if tokenKey == nil || tokenSecret == nil {
            let swifter = Swifter(consumerKey: TWITTER_CONSUMER_KEY, consumerSecret: TWITTER_CONSUMER_SECRET)

            swifter.authorize(
                withCallback: URL(string: "swifter-{Consumer API key}://")!,
                presentingFrom: self,
                success: { accessToken, response in
                    print(response)
                    guard let accessToken = accessToken else { return }
                    self.appStatus.twitterTokenKey = accessToken.key
                    self.appStatus.twitterTokenSecret = accessToken.secret
                    self.tweet()
            }, failure: { error in
                print(error)
            })

        } else {
            let swifter = Swifter(consumerKey: TWITTER_CONSUMER_KEY, consumerSecret: TWITTER_CONSUMER_SECRET, oauthToken: tokenKey!, oauthTokenSecret: tokenSecret!)

            swifter.postTweet(status: tweetMsgArea.text, success: { response in
                print(response)
            }, failure: { error in
                print(error)
            })
        }
    }
}

class AppStatus {
    var userdefault = UserDefaults.init(suiteName: "app_status")!

    var twitterTokenKey : String? {
        get {
            if let token : String = userdefault["token_key"] {
                return token
            } else {
                return nil
            }
        }

        set {
            userdefault["token_key"] = newValue
        }
    }

    var twitterTokenSecret : String? {
        get {
            if let secret : String = userdefault["token_secret"] {
                return secret
            } else {
                return nil
            }
        }

        set {
            userdefault["token_secret"] = newValue
        }
    }
}

extension UserDefaults {
    subscript<T: Any>(key: String) -> T? {
        get {
            if let value = object(forKey: key) {
                return value as? T
            } else {
                return nil
            }
        }
        set(_newValue) {
            if let newValue = _newValue {
                set(newValue, forKey: key)
            } else {
                removeObject(forKey: key)
            }
            synchronize()
        }
    }
}

ポイントとしてはUserDefaultsからアクセストークンを取得し、取得できなかった場合はSwifter.authorize()を呼び出してアクセストークンを取得します。
既に取得できていた場合はそのままSwifter.postTweet()で入力された内容をTweetします。

5. トラブルシュート

dyld: Library not loaded: ... Reason: image not foundと出る場合

Embedded BinariesにSwifteriOSが設定されていないことが原因。
下記のように設定を行う。