Unyablog.

のにれんのブログ

iOS で Slack に投稿するアプリを作り実機にインストール

iOS チュートリアルが一通り終わったので、Slack に投稿するアプリを作ってみることにしました。

レポジトリは以下。

元々 Android 版をシュッと作っていたので、iOS 版という位置づけでやってみます。

アプリの機能は非常に単純で、webhook の URL を入力し、ユーザー名、アイコン、文字列を指定して OK を押すと JSON を webhook サーバーに POST するというものです。

UI を作る

Andorid では適当に EditText を並べたものでしたが、iOS ではそのような UI はあまり見ないので UITableViewUITextField を入れることにしました。

f:id:nonylene:20160304010218p:plain

↑のようにUIを組み立てていきます。

UITAbleView で予め表示する要素が決定しているときは Static Cells を使います。StoryBoard 上で内容を決定しておくとUITableView で表示する部分のコードを書かなくて済むので楽。

f:id:nonylene:20160304010357p:plain:w300

Cell の内部全体に UITextField を入れています。

f:id:nonylene:20160304022854p:plain

ここで、特に何も設定せずに左端に置くと親の margin がうまく反映されずに左に寄りすぎてしまいました。

f:id:nonylene:20160304023740p:plain

これを防ぐには親の Table View CellContent ViewPreserve SuperView Margins のチェックを入れて、再度 Update Frames をクリックします。

すると、正しい位置になってくれました。

f:id:nonylene:20160304023110p:plain

ここで、本文に複数行入れたいときは UITextField ではなく UITextView を使いますが、Static Cells だと改行した時に上手く Cell が広がってくれなかったので諦めました。*1

あとは Done ボタンを設置したり、タイトルを設定したりして完成です。

Done ボタンを押した処理を書く

メッセージ送信

次は Done を押した時にメッセージを送信する処理を書きます。ネットワークを使う場合、AFNetWorking が著名なようですが、今回は学習の意味を兼ねてライブラリを使わずに POST することにします。

swift で POST するには以下の記事の通りに行えばよいです。

プログレス表示のために CocoaPods を導入

さて、何も表示しないと不安なのでプログレスダイアログを表示することにします。iOSプログレスダイアログといえば MBProgressHUD が有名みたいなので、それを使うことにしました。*2

ライブラリを簡単に管理するために Swift / Obj-C の依存性管理ツールである CocoaPods を導入します。

まずは bundle の Gemfile と同じように Podfile を書きます。

platform :ios, '9.0'
use_frameworks!

pod 'MBProgressHUD', '~> 0.9.2'

そして pod install すると {MyApp}.xcworkspace というフォルダが作られるので、一旦 Xcode を閉じて xcworkspace を開きます。

すると CocoaPods 経由でインストールしたライブラリが使えるようになります 🎉

プログレス表示

MBProgressHUD を使ってプログレスを出し、投稿できたらチェックマークを出します。ダメなら Alert を出すというコードを書きました。

// show progress
let progress = MBProgressHUD.showHUDAddedTo(self.view, animated: true)
progress.mode = .Indeterminate
progress.labelText = "Sending"

NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler:  {data, response, error in
    // main thread
    dispatch_async(dispatch_get_main_queue(), {
        var errorMessage: String?
        if let error = error {
            errorMessage = error.localizedDescription
        } else if (response as! NSHTTPURLResponse).statusCode / 100 != 2 {
            errorMessage = NSString(data: data!, encoding: NSUTF8StringEncoding)! as String
        }
        if let message = errorMessage {
            progress.hide(true)
            // show error alert
            let alert = UIAlertController(title: "Message not sent", message: message, preferredStyle: .Alert)
            alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
            self.presentViewController(alert, animated: true, completion: nil)
        } else {
            // show check mark
            progress.customView = UIImageView(image: UIImage(named: "CheckMark"))
            progress.mode = .CustomView
            progress.labelText = "Message sent"
            progress.hide(true, afterDelay: 1)
        }
    })
}).resume()

こうしてエラーハンドリングとプログレスができました。

Slack にもちゃんと投稿されています。

f:id:nonylene:20160304014554p:plain

アプリ再開時にURLなどを入れておく

毎回立ち上げるたびに URL を入力するのはやや面倒なので、NSUserDefaults を使って入力データを保存します。

struct Key {
    static let textKey = "text"
    static let userKey = "user"
    static let iconKey = "icon"
    static let webHookKey = "webhook"
}

override func viewDidLoad() {
    super.viewDidLoad()

    // recover data
    let userDefaults = NSUserDefaults.standardUserDefaults()
    textTextField.text = userDefaults.stringForKey(Key.textKey)
    userTextField.text = userDefaults.stringForKey(Key.userKey)
    iconTextField.text = userDefaults.stringForKey(Key.iconKey)
    webHookTextField.text = userDefaults.stringForKey(Key.webHookKey)
}

@IBAction func sendMessage(sender: UIBarButtonItem) {
    // save data
    let text = textTextField.text ?? ""
    let icon = iconTextField.text ?? ""
    let username =  userTextField.text ?? ""
    let url = webHookTextField.text ?? ""

    let userDefaults = NSUserDefaults.standardUserDefaults()
    userDefaults.setObject(text, forKey: Key.textKey)
    userDefaults.setObject(username, forKey: Key.userKey)
    userDefaults.setObject(icon, forKey: Key.iconKey)
    userDefaults.setObject(url, forKey: Key.webHookKey)

    ~~~ 
}

こうして、再開時にも送信時のデータが復元されるようになりました。

実機でデバッグしてみる

特に目立った機能もないので AppStore に申請する気はないですが実機で動かしたいですね。実機で動かすのは Xcode 7 から無料化されたということなので動かしました。

.xcodeproj を開くと IdentityTeam の欄があるのですが、実機を特に登録していない場合 Fix issue というボタンが表示されています。

実機を mac につなぎ、 Fix issue を押せば実機の登録が始まります。Processing Symbol Files... とバーに出てくるので数分待つと登録が完了し、実機へのインストールができるようになります。

f:id:nonylene:20160304020133p:plain

そしてデバイス欄で実機を選択し、インストールします。

ここで特に設定していない場合、実機で Your device management settings do not allow ~ というエラーが出て起動できないのですが、

設定アプリ -> General -> Profiles & Device Management を開くと開発者を承認する画面が出てくるので、ここで承認すると起動できるようになります。

ライセンス画面を作る

最後にライセンス画面を作成します。CococaPods にはライセンスの plist を作る機能があり、それを利用することで簡単にできました。

下の記事の通りにすると良いです。

こうしてアプリは完成しました!やっぱり無料でも実機で動かせるとだいぶ違いますね。

感想・その他

  • iOS、検索すると obj-C のコードが割りと出てくるのですが obj-C 全然わからないのでちょっとつらかった。まぁこの程度なら swift を検索ワードに入れれば出てくるので良いのですが。
    • obj-C 思ったよりよく分からん…
  • CocoaPods はだいぶ楽で良いですね。bundle と同じようなインターフェースで使いやすい。
  • UITextFieldTableView に入れると時々項目が選択されたかのように項目の背景色が変化してフォーカスが移らない時がありました。これは UITextField ではなく周りの margin 部分をタップしたことが原因で、本来は全体をタップしてもフォーカスが合うようにしたほうが良いのですが、
override func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    return false
}

とすることによって背景色を変化させないようするとマシになりました。

  • Swift は最高👌
    • といっても多分冗長なコードになっているんだろうなあ…
  • Android だと一時間ぐらいでつくれたけどなんだかんだ五時間ぐらいかかりました。iOS プログラミング、結構おもしろい*3し他のライブラリとかも触ってみたい。他にも何か作ってみたいです。

入門した記事

*1:Dynamic Cells の場合は、 http://qiita.com/mishimay/items/619f9ce60b4fabc1612f の通りにすればうまく広がってくれました。

*2:このプログレスバー、各所で見るのでiOSが元々提供しているものだと思っていました。

*3:補完とかドラッグめんどくさいみたいな不満はありますが…