こんにちは
実務としてiOSエンジニアをはじめて半年が経ちSwiftやXcodeに関する知見が溜まってきたので、Objective-C時代に作っていたやつを~~( への邪念で)~~Swift 3.0で書き換えたりしました。その際にハマったところや、そもそものクロージャまわりの知識が若干足りず右往左往したりしたところがあったので反省がてらViewController.swiftを晒していきたいと思います。
Codes
やったこと
Accounts
, Social
ライブラリをimport
import Accounts
import Social
ViewControllerでグローバルに使う変数を宣言
var accountStore: ACAccountStore = ACAccountStore()
var twitterAccount: ACAccount?
var tweets: [Tweet] = []
Tweet
構造体を宣言
import Foundation
struct Tweet {
let text: String
let createdAt: String
let user: User
var dump: String {
get {
return "\(text) by @\(user.screenName)"
}
}
}
struct User {
let name: String
let screenName: String
let profileImageURLHTTPS: String
}
viewDidLoad()
getAccounts { (accounts: [ACAccount]) -> Void in
self.showAccountSelectSheet(accounts: accounts)
}
ちょっと冗長+ここだけでgetTimeline()
をしているのがパッと見わからないのでリファクタリングしたい
getAccounts(callback: @escaping ([ACAccount]) -> Void)
func getAccounts(callback: @escaping ([ACAccount]) -> Void) {
let accountType: ACAccountType = accountStore.accountType(withAccountTypeIdentifier: ACAccountTypeIdentifierTwitter)
accountStore.requestAccessToAccounts(with: accountType, options: nil) { (granted: Bool, error: Error?) -> Void in
guard error == nil else {
print("error! \(error)")
return
}
guard granted else {
print("error! Twitterアカウントの利用が許可されていません")
return
}
let accounts = self.accountStore.accounts(with: accountType) as! [ACAccount]
guard accounts.count != 0 else {
print("error! 設定画面からアカウントを設定してください")
return
}
print("アカウント取得完了")
callback(accounts)
}
}
(循環参照になってないか後で見ないと...)
showAccountSelectSheet(accounts: [ACAccount])
private func showAccountSelectSheet(accounts: [ACAccount]) {
let alert = UIAlertController(title: "Twitter", message: "Choose an account", preferredStyle: .actionSheet)
for account in accounts {
alert.addAction(UIAlertAction(title: account.username, style: .default, handler: { [weak self] (action) -> Void in
if let unwrapSelf = self {
unwrapSelf.twitterAccount = account
unwrapSelf.getTimeline()
}
}))
}
alert.popoverPresentationController?.sourceView = self.view
alert.popoverPresentationController?.sourceRect = CGRect(x: self.view.bounds.size.width / 2, y: self.view.bounds.size.height / 2, width: 1.0, height: 1.0)
alert.popoverPresentationController?.permittedArrowDirections = .down
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
getTimeline()
private func getTimeline() {
let url = URL(string: "https://api.twitter.com/1.1/statuses/home_timeline.json?count=100")
guard let request = SLRequest(forServiceType: SLServiceTypeTwitter, requestMethod: .GET, url: url, parameters: nil) else {
return
}
request.account = twitterAccount
request.perform { (responseData, response, error) -> Void in
if error != nil {
print(error ?? "error in performing request :[")
} else {
do {
guard let responseData = responseData else {
return
}
let result = try JSONSerialization.jsonObject(with: responseData, options: .allowFragments)
for tweet in result as! [AnyObject] { // errorsが返ってくることがある
guard let text = tweet["text"] as? String, let createdAt = tweet["created_at"] as? String else { // これmodel側でやるべきな感じ
print("failed to map tweet string from JSON")
return
}
let user = tweet["user"] as? [String: Any]
guard let userName = user?["name"] as? String, let userScreenName = user?["screen_name"] as? String, let userProfileImageURLHTTPS = user?["profile_image_url_https"] as? String else {
print("failed to map user string from JSON")
return
}
let tweetObject = Tweet(text: text, createdAt: createdAt, user: User(name: userName, screenName: userScreenName, profileImageURLHTTPS: userProfileImageURLHTTPS))
self.tweets.append(tweetObject)
}
// ここでなにかする
// (例えば self.tableView.reloadData() とか)
} catch let error as NSError {
print(error)
}
}
}
}
がんばったところ
リクエスト叩いた時のエラー
Couldn't authenticate you的なのが出た。
iOSに紐付けていたTwitterアカウントが古いものだったっぽく、設定アプリからパスワードを入力し直したらいけました。
モデルのマッピングしんどい
しんどい。
でもそれ以上に、こんだけのアプリで SwiftyJSON をインポートして依存性に縛られるのもなぁ。
とゆことで手でマッピングしてみました。
とりあえず自分が欲しかった、名前とかアイコンの画像URLとか、そこらへんを引っ張っています。他の要素が必要でしたらTweet.swiftでプロパティを増やして、getTimeline()のクロージャの中で引っ張ってくれば良いかとおもいます。
Swiftのcomputed properties便利
詳しくは こちら 。
ツイートをprint()で確認したいときにとりあえずcomputed propertiesを叩けばいいから便利。 "\(tweet.text) by \(tweet.user.name)"
とか毎回打つのは大変なので。