- 投稿日:2019-02-03T23:16:37+09:00
iOSアプリとTwitter連携
はじめに
Social.frameworkまたはUIActivityViewControllerが使えなくなったしまったので、TwitterKit3.4.0で、ツィートするところまで作ってみました。
手順は
- Twitter Developersでアプリ登録(本記事では省略)
- TwitterKitのインストール
- プロジェクトの設定
- 認証部実装
- ツィート実行部実装
で、実装しました。
TwitterKitのインストール
今回はCocoaPodsを使用しました。
Podfileは# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'twitterKitSmp' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! # Pods for twitterKitSmp pod 'TwitterKit' <-- 追加 endのような感じです。
プロジェクトのディレクトリで
% pod init <Podfileが作成されるので、pod 'TwitterKit'を追加> % pod installTwitterKitのインストールは以上です。
プロジェクトの設定
info.plistに下記の追加を行います。
<key>CFBundleURLTypes</key> <array> <dict> <!-- 追加ここから --> <key>CFBundleURLSchemes</key> <array> <string>twitterkit-{CONSUMERKEY}</string> </array> <!-- 追加ここまで --> </dict> </array> <!-- 追加ここから --> <key>LSApplicationQueriesSchemes</key> <array> <string>twitter</string> <string>twitterauth</string> </array> <!-- 追加ここまで -->CFBundleURLTypes.CFBundleURLSchemesとLSApplicationQueriesSchemesを追加します。
CFBundleURLSchemesはTwitterAPI側のCallbackURLにも追記します。認証部実装
まず、TwitterKitの初期設定等を行います。
AppDelegate.swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. // 1. TwitterAPIのAPI keyとAPI secret keyを設定 TWTRTwitter.sharedInstance().start(withConsumerKey: "{CONSUMERKEY}", consumerSecret: "{CONSUMERSECRET}") return true } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { // 2. Twitter認証からのコールバック if TWTRTwitter.sharedInstance().application(app, open: url, options: options) { return true } return false }
- アプリ起動時にTwitterAPIのAPI keyとAPI secret keyを設定します。
- Twitter認証からのコールバックを受け取ります。
次に認証開始部分を実装します。
ViewController.swiftfunc sndTweet() { // 1.ログインされているか? if TWTRTwitter.sharedInstance().sessionStore.hasLoggedInUsers() { // 2.ツィート開始 sndTweetExec() } else { // 3.認証開始 TWTRTwitter.sharedInstance().logIn(with: self, completion: { (session, error) in if let sess = session { print("Signed in as \(sess.userName)") // 4.ツィート開始 self.sndTweetExec() } else { // 5.認証失敗 print("login error: \(error?.localizedDescription)") } }) } }
- Twitterにログインされているか確認します。
- 既にログインされているため、ツィート開始へ進みます。
- Twitter認証を開始します。Twitterのログイン画面に遷移します。ログイン後、アプリの連携を許可するか聞かれるので許可すればアプリへ戻ります。
- 認証が成功したのでツィート開始へ進みます。
- 認証失敗したのでここで終わります。
ツィート実行部実装
ツィートはTwitterKitのTWTRComposerViewControllerを使用しました。
またツィート実行後にアラートを出すように実装します。ViewController.swiftfunc sndTweetExec() { let str:String = "サンプルツィート" // 1.コントローラー初期化 let comp = TWTRComposerViewController.init(initialText: str, image: nil, videoData: nil) // 2.デレゲート comp.delegate = self // 3.コントローラ表示 present(comp, animated: true, completion: nil) }
- TWTRComposerViewControllerを初期化します。今回はテキストのみ。
- デレゲートを設定します。デレゲートはキャンセルとツィート成功、失敗が定義されています。
- コントローラーを表示します。
次にデレゲートの処理を実装します。
ViewController.swiftextension ViewController: TWTRComposerViewControllerDelegate { // キャンセル時 func composerDidCancel(_ controller: TWTRComposerViewController) { print("Cancel") } // ツィート失敗時 func composerDidFail(_ controller: TWTRComposerViewController, withError error: Error) { print("Error") let store = TWTRTwitter.sharedInstance().sessionStore if let userID = store.session()?.userID { store.logOutUserID(userID) } dismiss(animated: false, completion: nil) DispatchQueue.main.async { self.tweetAlert(memo:"Twitterに投稿に失敗しました") } } // ツィート成功時 func composerDidSucceed(_ controller: TWTRComposerViewController, with tweet: TWTRTweet) { print("Ok") dismiss(animated: false, completion: nil) DispatchQueue.main.async { self.tweetAlert(memo: "Twitterに投稿しました。\nご協力ありがとうございます。") } } }今回はツィート実行後にアラートを表示したいため、デレゲートメソッド内でdismiss()をコールして、Viewを閉じています。
DispatchQueue.main.asyncは必要なかったかも知れませんが、念のため。。サンプルアプリ
![]()
![]()
![]()
こんな感じのアプリにしてみました。
ソースコード
にソース一式を置きました。まとめ
以上、ざっとまとめましたが、木になる点として
- CallbackURLが指定できなかった。
指定方法がありそうな気がしましたが、見つけられませんでした。- ツィート画面のカスタマイズしたい。
できそうな感じがしたのですが、今回はできませんでした。
次回、機会があれば再トライしてみます。なお、下記の記事を参考にさせていただきました。ありがとうございました。
- 投稿日:2019-02-03T22:06:48+09:00
ようやくGUI! SwiftでUIKitのLabel部品でHello World
はじめに
ずっとPlaygroundでコンソール(?)プログラミングをしてきた。
Swiftのコード文法てきな部分はわかってきたので
統合開発環境としてのXCodeの使い方を含めて少しずつ挑戦の幅を広げる。手順概略
GUIアプリになると、途端に取り扱うファイルが増えるので
コードを書くだけでは解決しなくなる。
ここではざっくり手順を載せる。
- [Main.storyboard]を開く
- ラベルを配置する
- [アシスタントエディタ]で配置したラベルをソースコードへ取り込む
ラベルの定義@IBOutlet weak var label: UILabel!
- ソースコード上でラベルにテキストを設定する
ラベルにテキストを設定label.text = "Hello World"ソースコード全文
ViewController.swiftclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() label.text = "Hello World"//<-手打ちで追加 } @IBOutlet weak var label: UILabel!//<-アシスタントエディタで追加 }
- 投稿日:2019-02-03T19:45:56+09:00
テスタブルコードの研究
はじめに
おみくじクラスを作ってみた でおみくじを引く関数を作った。
動作は問題ないが、よりテスタブルなコードへ書き換える。検討
現在のソースコードは以下の通り。
元コードfunc get() -> String { let result = base.randomElement()! //ランダムに配列要素を取り出す let dateStr = toDateStr(date: Date()) //現在時刻を文字列へ変換 history.append(dateStr + " " + result) //履歴配列へ[結果+日時]を追加 return result //おみくじ結果を返す }テストがやりにくい点は以下の点である。
- resultは値がランダム
- dateStrは値が実行時に変化する
- historyはメンバ変数
resultのランダムは必須機能なので仕方ない。
それ以外はget関数から追い出す。get関数から履歴記録部分を別関数へ分離func get() -> String { let result = base.randomElement()! //ランダムに配列要素を取り出す setHistory(result: result) //履歴を記録 return result //おみくじ結果を返す }さらに履歴文言の生成は別関数へ追い出し、必要な情報を渡すようにする。
履歴文言の生成を追い出すfunc setHistory(result: String) { //履歴記録用文字列の生成 let dateStr = toHistoryString(result: result, date: Date()) //履歴配列へ結果を追加 history.append(dateStr + " " + result) }履歴文言の生成は、外部の情報を元に組み立てる関数に閉じる
履歴文言生成関数func toHistoryString(result: String, date: Date) -> String { let dateStr = toDateStr(date: date) //時刻を文字列へ変換 return (dateStr + " " + result) //履歴配列へ[結果+日時]を返却 }結果
今回はテストコードまでは書かなかったが
履歴文言生成関数は、入力が決まれば出力が一意に決まる「テスタブル」なコードができた。
- 投稿日:2019-02-03T16:26:07+09:00
AR顔認識アプリを開発して原宿で街頭インタビューしてYouTubeにアップした話
2019年1月からエンジニア系YouTubeを始めました。今YouTubeにあるエンジニア系の動画は「エンジニアは稼げる」とか「フリーランスエンジニア自由最高」みたいな動画が多く、それはそれいいんですが、技術で攻めて駆逐せねばならぬという僕の謎の正義感が働いているため頑張っています。
基本的にはARやエンジニアの生活について発信するチャンネルですが、今回はいかにもYouTuberっぽい動画且つエンジニアらしくアプリも作ったのでその話を紹介していきたいと思います。
企画
先輩YouTuberでxRエンジニア界隈の友人である @nkjzm と一緒に企画を考えました。
要件は2つ。
- K-BOYがARエンジニアなのでARに沿った内容にしたい
- せっかくコラボするのでインパクトがある内容にしたい
この要件のなかで昨日の13:30-14:00くらいでディスカッションし、14:00-15:00でアプリ開発と撮影準備をし、原宿に飛び出したわけであります。
インパクトを残すという意味で少し下ネタを絡めるというテーマでディスカッションしていました。目的は「IT界隈じゃない人も興味を持ってくれる動画にしよう!」だったので、ギリギリ大丈夫そうな下ネタの経験人数というテーマにしました。
そしてARを絡めるために最終的にたどり着いたのが「ARKitの顔認識を使って顔を読み取って経験人数を予測するアプリ」
この企画プロセスはハッカソンっぽいなあと思います。
アプリ仕様
ARKitのFaceDetectionを使って顔をスキャン感を出す
詳しくはコードをご覧ください。
iPhoneX等で使えるフロントカメラの顔認識を使用しています。
顔のgeomertyをupdateし続けて、あみあみのmaterialを張っているだけで、サンプルコードレベルの実装です。
ゲージをアニメーションさせてスキャン感をだす
UIProgressViewをいじったカスタムクラスを以前作ったことがあったので、同じように再現実装しました。
起動から5秒後に、10秒かけてアニメーションさせてます。
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.titleLabel.isHidden = false UIView.animate(withDuration: 5, delay: 0, options: [], animations: { self.hpView.hpBar.setProgress(1, animated: true) }, completion: nil) DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.titleLabel.text = "経験人数は..." self.countLabel.isHidden = false } }経験人数は適当に配列で用意
もちろん、ネタ企画なので経験人数は配列に入れときます。最速で実装し、原宿に飛び出して使ってもらうのが目的なため、複雑なアルゴリズムを作っている暇はありません。リアルっぽさを出すために少ない人数を多めにしときました。
let array: [String] = [ "0人", "1人", "2人", "3人", "4人", "5人", "6人", "7人", "8人", "11人", "12人", "14人", "18人", "19人", "22人", "28人", "35人", "100人", "160人", ]このarrayの中からrandomで一個選びます
countLabel.text = self.array.randomElement()!ユーザーの反応
詳しくはYouTubeを見てもらいたいと思いますが、ウケてました。本当にアルゴリズムがあるかどうかは重要ではないということがわかりました。
「違うよ!」「え!当たってる!」など反応を楽しむことが目的。
その目的は達成できたのではないかなと思います。嬉しいです。
動画制作
- 撮影はiPhone XS Maxで1時間半
- 動画編集はFinal Cut Proで4時間くらい
- サムネはAdobe Illustlatorで30分くらい
まとめ
アプリを作ってインタビュー、それを動画にするというエンジニアらしいYouTube制作ができたのではないかと思います。
エンジニアといえばブログのイメージが強いですが、より多くの層にリーチするためにYouTubeを撮ってみるのはいかがでしょうか?想像以上にクリエイティブな作業で、編集の大変さがわかると思います。
では!
- 投稿日:2019-02-03T10:52:14+09:00
swift あまり知られていない? Stride型 [初心者向け]
- 投稿日:2019-02-03T07:05:07+09:00
Swift:NSTableViewの項目をドラッグで入れ替えできるようにする
概要
TableViewの項目をドラッグで入れ替えられるようにしようと思ったら、MacかつMojave以降だとかなり厄介かつTipsが全然ないことに気づきました。忘備録!ソース
下準備class ViewController: NSViewController { @IBOutlet weak var myTableView: NSTableView! @IBOutlet weak var segmentedControl: NSSegmentedControl! private var data = [String]() override func viewDidLoad() { super.viewDidLoad() myTableView.delegate = self myTableView.dataSource = self // ↓これがドラッグを可能にする myTableView.registerForDraggedTypes([NSPasteboard.PasteboardType("public.data")]) myTableView.append("Apple") myTableView.append("Banana") myTableView.append("Grape") myTableView.append("Peach") myTableView.reloadData() segmentedControl.setEnabled(false, forSegment: 0) } @IBAction func removeData(_ sender: Any) { data.remove(at: myTableView.selectedRow) myTableView.reloadData() segmentedControl.setEnabled(false, forSegment: 0) } }本実装extension ViewController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { return data.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { // ここのIdentifierの指定の仕方要注意 StoryBoardでNSTableCellViewに対して設定する必要があります。 let cell = myTableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DataCell"), owner: self) as? NSTableCellView cell?.textField?.stringValue = data[row] return cell } func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { segmentedControl.setEnabled(true, forSegment: 0) return true } func tableViewSelectionDidChange(_ notification: Notification) { for i in (0 ..< data.count) { if myTableView.isRowSelected(i) { return } } segmentedControl.setEnabled(false, forSegment: 0) } // ここから下がドラッグ機能に必要 func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { if dropOperation == .above { return .move } return [] } func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { do { // Mojaveから扱いが難しくなった部分,Tipsほぼ0 let data = try NSKeyedArchiver.archivedData(withRootObject: rowIndexes, requiringSecureCoding: false) pboard.declareTypes([NSPasteboard.PasteboardType("public.data")], owner: self) pboard.setData(data, forType: NSPasteboard.PasteboardType("public.data")) } catch { Swift.print(error) } return true } func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { let pboard = info.draggingPasteboard if let pboardData = pboard.data(forType: NSPasteboard.PasteboardType("public.data")) { do { if let rowIndexes = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(pboardData) as? IndexSet { myTableView.beginUpdates() for oldRow in rowIndexes { if oldRow < row { myTableView.moveRow(at: oldRow, to: row - 1) data.swapAt(oldRow, row - 1) } else if row < data.count { myTableView.moveRow(at: oldRow, to: row) data.swapAt(oldRow, row) } } myTableView.endUpdates() return true } } catch { Swift.print(error) } } return false } }
- 投稿日:2019-02-03T06:37:31+09:00
Swift:Macのデスクトップを一瞬で綺麗にするツールつくった
概要
開発中、どうしても一時的にデスクトップが汚くなってしまうことよくありませんか?僕はいつもそれで困っていました。そこで、ショートカット一発でデスクトップを綺麗にしてくれるツール作ったろと思い。チャチャっとツールを作ってみました。
成果物
あらかじめ設定しておいたルールに従って、デスクトップのファイルやフォルダが仕分けされます。
成果物のGithubリンクはこちら
Desktop Cleaner
Preferencesで整理を発動するショートカットキーを設定できます。
また、ファイルやフォルダを整理するルールを追加できます。
ルールは一覧の上から優先されて実行されます。(順番はドラッグで変更できます。)開発中の困難
配布方法
DownloadsフォルダやDocumentsフォルダはアクセス可能なのですが、DesktopへのアクセスはApp SandBoxにて禁止されます。そのため、当初はApp Storeにてリリースしようかと思っていましたが、Githubにてオープンソースとして公開することにしました。デスクトップを一瞬隠したい!
色々やった結果、背景画像を部分的に用いることで隠すことにしました。ここが一番実装上時間かかった。外部の
NSWindowアクセスできなさすぎでしょ...
Swift:Macのデスクトップ背景画像を取得するテーブルビューの項目をドラッグ&ドロップで入れ替えたい!
たったこれだけのことがめちゃくちゃ厄介で2時間悩んだ。
Swift:NSTableViewの項目をドラッグで入れ替えできるようにする最後に
指定できるルールがすくねぇよって方はGithubからForkして加工して下さいm(_ _)m
- 投稿日:2019-02-03T06:06:57+09:00
[iOS] [Swift] Sequence Protocolのサンプルコード(社内勉強会資料)
はじめに
弊社内の勉強会で、Sequence Protocolの中でも頻繁に使うモノをサンプルコード付きで紹介しました。
ごくシンプルなコードですが、せっかく書いたものを社内だけにとどめることは勿体無いので公開いたします。参加者に「お題」だけを配って(答えを削って)、ハンズオンなどをやってみると楽しいと思います!
環境
・Xcode 10.1
・Swift 4.2サンプルコード
Sequence.playgroundimport UIKit struct Student { var code: String var firstName: String var lastName: String var score: Int var fullName: String { return firstName + " " + lastName } } var students = [Student]() students.append(Student(code: "AB100000", firstName: "Taro", lastName: "Yamada", score: 62)) students.append(Student(code: "AB100001", firstName: "Ichiro", lastName: "Suzuki", score: 81)) students.append(Student(code: "AB100002", firstName: "Hanako", lastName: "Sato", score: 96)) students.append(Student(code: "AB100003", firstName: "Jiro", lastName: "Takahashi", score: 58)) students.append(Student(code: "AB100004", firstName: "Ichiro", lastName: "Tanaka", score: 75)) students.append(Student(code: "AB100005", firstName: "Hanako", lastName: "Yamada", score: 96)) // 70点以上の生徒を抽出する let filtered = students.filter { (student) -> Bool in return student.score >= 70 } for (i, student) in filtered.enumerated() { print("70点以上の生徒\(i+1)人目は\(student.fullName)です。") } /* 70点以上の生徒1人目はIchiro Suzukiです。 70点以上の生徒2人目はHanako Satoです。 70点以上の生徒3人目はIchiro Tanakaです。 70点以上の生徒4人目はHanako Yamadaです。 */ // "コード, フルネーム"という形式の文字列の配列に変換する let stringArray = students.map { (student) -> String in return "\(student.code), \(student.fullName)" } stringArray.forEach { print($0) } /* AB100000, Taro Yamada AB100001, Ichiro Suzuki AB100002, Hanako Sato AB100003, Jiro Takahashi AB100004, Ichiro Tanaka AB100005, Hanako Yamada */ // 全生徒の点数を合計する let total = students.reduce(0) { (total, student) -> Int in return total + student.score } print("全生徒のScoreの合計は\(total)点です") /* 全生徒のScoreの合計は468点です */ // key=コード、value=フルネームというDictionaryに変換する let dic = students.reduce(into: [String: String]()) { (dic, student) in return dic[student.code] = student.fullName } dic.forEach { print($0) } /* (key: "AB100000", value: "Taro Yamada") (key: "AB100001", value: "Ichiro Suzuki") (key: "AB100003", value: "Jiro Takahashi") (key: "AB100004", value: "Ichiro Tanaka") (key: "AB100005", value: "Hanako Yamada") (key: "AB100002", value: "Hanako Sato") */ // Satoが含まれているか? let existsSato = students.contains { (student) -> Bool in return student.lastName == "Sato" } print("Satoさんが含まれているか = \(existsSato)") /* Satoさんが含まれているか = true */ // Yamadaさんの1件目を取得する let first = students.first { (student) -> Bool in return student.lastName == "Yamada" } if let first = first { print(first.fullName) } else { print("見つかりませんでした") } /* Taro Yamada */ // FirstName>LastName順にソートする let sorted = students.sorted { (student1, student2) -> Bool in if student1.firstName == student2.firstName { return student1.lastName < student2.lastName } else { return student1.firstName < student2.firstName } } for (i, student) in sorted.enumerated() { print("FirstName>LastName順の生徒\(i+1)人目は\(student.fullName)です。") } /* FirstName>LastName順の生徒1人目はHanako Satoです。 FirstName>LastName順の生徒2人目はHanako Yamadaです。 FirstName>LastName順の生徒3人目はIchiro Suzukiです。 FirstName>LastName順の生徒4人目はIchiro Tanakaです。 FirstName>LastName順の生徒5人目はJiro Takahashiです。 FirstName>LastName順の生徒6人目はTaro Yamadaです。 */勉強会で使えるリファレンス
サンプルコードで紹介した以外にも、便利なメソッドはたくさんあるかと思います。
SwiftDoc.org は、
・サンプルコードが載っている
・一覧性が優れていて探しやすい
ことから、勉強会の教材として使いやすいと思います。Sequence Protocolの項はこちらになります。
https://swiftdoc.org/v4.2/protocol/sequence/







