20190203のSwiftに関する記事は8件です。

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 install

TwitterKitのインストールは以上です。

プロジェクトの設定

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.swift
func 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
  }
  1. アプリ起動時にTwitterAPIのAPI keyとAPI secret keyを設定します。
  2. Twitter認証からのコールバックを受け取ります。

次に認証開始部分を実装します。  

ViewController.swift
func 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)")
      }
    })
  }
}
  1. Twitterにログインされているか確認します。
  2. 既にログインされているため、ツィート開始へ進みます。
  3. Twitter認証を開始します。Twitterのログイン画面に遷移します。ログイン後、アプリの連携を許可するか聞かれるので許可すればアプリへ戻ります。
  4. 認証が成功したのでツィート開始へ進みます。
  5. 認証失敗したのでここで終わります。

ツィート実行部実装

ツィートはTwitterKitのTWTRComposerViewControllerを使用しました。

またツィート実行後にアラートを出すように実装します。

ViewController.swift
func 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)
}
  1. TWTRComposerViewControllerを初期化します。今回はテキストのみ。
  2. デレゲートを設定します。デレゲートはキャンセルとツィート成功、失敗が定義されています。
  3. コントローラーを表示します。

次にデレゲートの処理を実装します。

ViewController.swift
extension 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が指定できなかった。
    指定方法がありそうな気がしましたが、見つけられませんでした。
  • ツィート画面のカスタマイズしたい。
    できそうな感じがしたのですが、今回はできませんでした。
    次回、機会があれば再トライしてみます。

なお、下記の記事を参考にさせていただきました。ありがとうございました。

https://qiita.com/naoto0n2/items/8d80dde2584d970317e7

iOS11のTwitter投稿対応(Social.framework → TwitterKit)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ようやくGUI! SwiftでUIKitのLabel部品でHello World

はじめに

ずっとPlaygroundでコンソール(?)プログラミングをしてきた。
Swiftのコード文法てきな部分はわかってきたので
統合開発環境としてのXCodeの使い方を含めて少しずつ挑戦の幅を広げる。

手順概略

GUIアプリになると、途端に取り扱うファイルが増えるので
コードを書くだけでは解決しなくなる。
ここではざっくり手順を載せる。

  1. [Main.storyboard]を開く
  2. ラベルを配置する
  3. [アシスタントエディタ]で配置したラベルをソースコードへ取り込む
ラベルの定義
@IBOutlet weak var label: UILabel!
  1. ソースコード上でラベルにテキストを設定する
ラベルにテキストを設定
label.text = "Hello World"

ソースコード全文

ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = "Hello World"//<-手打ちで追加
    }

    @IBOutlet weak var label: UILabel!//<-アシスタントエディタで追加
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テスタブルコードの研究

はじめに

おみくじクラスを作ってみた でおみくじを引く関数を作った。
動作は問題ないが、よりテスタブルなコードへ書き換える。

検討

現在のソースコードは以下の通り。

元コード
func get() -> String {
    let result = base.randomElement()!      //ランダムに配列要素を取り出す
    let dateStr = toDateStr(date: Date())   //現在時刻を文字列へ変換
    history.append(dateStr + "  " + result) //履歴配列へ[結果+日時]を追加
    return result                           //おみくじ結果を返す
}

テストがやりにくい点は以下の点である。

  1. resultは値がランダム
  2. dateStrは値が実行時に変化する
  3. 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)    //履歴配列へ[結果+日時]を返却
}

結果

今回はテストコードまでは書かなかったが
履歴文言生成関数は、入力が決まれば出力が一意に決まる「テスタブル」なコードができた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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の顔認識を使って顔を読み取って経験人数を予測するアプリ

この企画プロセスはハッカソンっぽいなあと思います。

アプリ仕様

GIFイメージ-818991CA90C7-1.gif

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()!

ユーザーの反応

スクリーンショット 2019-02-03 16.17.27.png

詳しくはYouTubeを見てもらいたいと思いますが、ウケてました。本当にアルゴリズムがあるかどうかは重要ではないということがわかりました。

「違うよ!」「え!当たってる!」など反応を楽しむことが目的。

その目的は達成できたのではないかなと思います。嬉しいです。

動画制作

  • 撮影はiPhone XS Maxで1時間半
  • 動画編集はFinal Cut Proで4時間くらい
  • サムネはAdobe Illustlatorで30分くらい

まとめ

アプリを作ってインタビュー、それを動画にするというエンジニアらしいYouTube制作ができたのではないかと思います。

エンジニアといえばブログのイメージが強いですが、より多くの層にリーチするためにYouTubeを撮ってみるのはいかがでしょうか?想像以上にクリエイティブな作業で、編集の大変さがわかると思います。

では!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swift あまり知られていない? Stride型 [初心者向け]

Sting ではなくて Stride 型!

とりあえず、サンプルコードを見てみましょう!

ex
for x in stride(from:0, to:20, by:4){
     print(x, terminator:" ")
}//0 4 8 12 16 " と表示される。

上記の例文のようにstride型はfor分と組み合わせることが多い!

サンプルコードを見てわかるように stide型は指定した範囲の中で~ごとに値を増やしながらループすることができます!

短いですが今回はこれで終わりです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift:Macのデスクトップを一瞬で綺麗にするツールつくった

概要

開発中、どうしても一時的にデスクトップが汚くなってしまうことよくありませんか?僕はいつもそれで困っていました。そこで、ショートカット一発でデスクトップを綺麗にしてくれるツール作ったろと思い。チャチャっとツールを作ってみました。

成果物

あらかじめ設定しておいたルールに従って、デスクトップのファイルやフォルダが仕分けされます。
desktop_cleaner.gif

成果物のGithubリンクはこちら
Desktop Cleaner

preferences.png
Preferencesで整理を発動するショートカットキーを設定できます。
また、ファイルやフォルダを整理するルールを追加できます。
ルールは一覧の上から優先されて実行されます。(順番はドラッグで変更できます。)

開発中の困難

配布方法

DownloadsフォルダやDocumentsフォルダはアクセス可能なのですが、DesktopへのアクセスはApp SandBoxにて禁止されます。そのため、当初はApp Storeにてリリースしようかと思っていましたが、Githubにてオープンソースとして公開することにしました。

デスクトップを一瞬隠したい!

色々やった結果、背景画像を部分的に用いることで隠すことにしました。ここが一番実装上時間かかった。外部のNSWindowアクセスできなさすぎでしょ...
Swift:Macのデスクトップ背景画像を取得する

テーブルビューの項目をドラッグ&ドロップで入れ替えたい!

たったこれだけのことがめちゃくちゃ厄介で2時間悩んだ。
Swift:NSTableViewの項目をドラッグで入れ替えできるようにする

最後に

指定できるルールがすくねぇよって方はGithubからForkして加工して下さいm(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS] [Swift] Sequence Protocolのサンプルコード(社内勉強会資料)

はじめに

弊社内の勉強会で、Sequence Protocolの中でも頻繁に使うモノをサンプルコード付きで紹介しました。
ごくシンプルなコードですが、せっかく書いたものを社内だけにとどめることは勿体無いので公開いたします。

参加者に「お題」だけを配って(答えを削って)、ハンズオンなどをやってみると楽しいと思います!:grinning:

環境

・Xcode 10.1
・Swift 4.2

サンプルコード

Sequence.playground
import 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/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む