Pragmatic ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

Jestで環境変数を設定する

JestでテストするときにwebpackのDefinePluginなどで環境変数をセットしている場合、テスト時にもセットしたいときがあります。

package.jsonのjestのところにglobalsを追加することで設定できます。

Configuring Jest · Jest

EXAMPLE

package.json

"jest": {
  "globals": {
    "ENDPOINT1": "http://api.example.com",
  }
}

ルートVueインスタンスのプロパティ

こんな感じにroot Vue instanceのpropsを使って初期値とかを与えたい場合があったりします。

  • html
<div id='root' props='initial-value'>
const vm = new Vue({
    el: '#root',
    props: ['initialValue'],
    data: {
        value: '',
    },
    beforeMount: function() {
        this.value = this.initialValue;
    }
});

が、これだとthis.initialValueはundefinedになります。

原因は、propsは親から子にデータを渡すためのもので、親のないroot vue instanceでは使えないようでした

Passing props to root instance · Issue #6440 · vuejs/vue · GitHub

対策1

真っ当な対策としてはroot vlue instanceのpropsを使って値を渡すのではなく、 rootに子コンポーネントを作って、子コンポーネントのpropsを使うです。

対策2

どうしてもrootで渡したい場合は、強引なやり方ですが、elementのattributesから取ってくれば一応とれます

const vm = new Vue({
    el: '#root',
    data: {
        value: '',
    }
    beforeMount: function() {
        this.value = this.$el.attributes['initial-value'].value;
    }
});

slackのbotでmentionが飛ばせなくなった対応

9/11からslackのusername指定が使えなくなりました。

A lingering farewell to the username | Slack

これにより、botなどでユーザーにメンション飛ばしていた場合にusernameを使っているとmetionが飛ばなくなります。

対応方法

<@username><@userID>に変更するだけです。

userIDを取得方法

以下からユーザーリストを取得してIDを取ってきます。

https://slack.com/api/users.list?token=<token>

Markdown Night 2017 Summer 資料まとめ

Markdown Night 2017 Summerの発表資料のまとめです。

イベントページ

connpass.com

ハッシュタグ

twitter.com

資料

なぜMarkdownは拡張されるのか

Markdownはなぜ拡張され続けるのか | bitjourney Kibela

esa Markdownの思想とデザイン

esa-pages.io

Markdownを拡張する話

speakerdeck.com

Qiita/Qiita:TeamにおけるMarkdownレンダリングの歴史

speakerdeck.com

TBD in Markdown Night

speakerdeck.com

MARKDOWNの本を一緒に作りたい

gitpitch.com

Vagrantでwebpackのwatchが動かない

Vagrant上でwebpackのwatchオプションを使ってファイルの変更を監視しても、変更を検知しないという現象に出くわしました。

こちらに(Webpack Watch in Vagrant/Docker )書いてあるように

webpackのconfigファイルに以下のようにpollingするようにする必要があります。

watchOptions: {
  poll: true
}

Ubuntu16.04にyarnを入れる

Ubuntu16.04にNode.jsの6系とyarnをインストー

sudo curl -sL https://deb.nodesource.com/setup_6.x | sudo bash -

sudo apt-get install -y nodejs

sudo curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 0.24.4

Ubuntu 16.04にDocker環境を作る

Ubuntu 16.04上でDocker動かすのにちょっと手こずったのでメモ。

とりあえずこれを実行すれば、Docker CEとdocker composeがインストールされるはず。

yes | sudo apt-get install \
           apt-transport-https \
           ca-certificates \
           curl \
           software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
        "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get update

sudo apt-get install -y docker-ce

apt-cache madison docker-ce

sudo curl -o /usr/local/bin/docker-compose -L https://github.com/docker/compose/releases/download/1.13.0/docker-compose-`uname -s`-`uname -m`

sudo usermod -aG docker $USER

sudo chmod +x /usr/local/bin/docker-compose

Fetch APIを使ってRailsのAPIを叩く

JavaScript(es2015)でFetch APIを使ってRailsAPIを呼ぶときの方法。

ポイントとしては、'credentials: same-origin'をつけることと、 CSRFを有効にしている場合はheaderにX-CSRF-Tokenをつける点です。

    const getCsrfToken = () => {
        const metas = document.getElementsByTagName('meta');
        for (let meta of metas) {
            if (meta.getAttribute('name') === 'csrf-token') {
                console.log(meta.getAttribute('content'));
                return meta.getAttribute('content');
            }
        }
        return '';
    }

    const postFoo = () => {
        return fetch('/api/foo', {
            method: 'POST',
            credentials: 'same-origin',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRF-Token': getCsrfToken()
            },
            body: JSON.stringify({
                bar: "bar",
            })
        }).then((response) => {
            if (response.status >= 200 && response.status < 300) {
                return response.json();
            } else {
                const error = new Error(response.statusText);
                throw error;
            }
        });
    }

iOSのFrameworkのVersionについて

Frameworkにはversionがいくつかあってそれらの違いの雑な説明です。

その1

ひとつはお馴染みのinfo.plistに書くやつ これはちょっと省きます

その2

Umbrella Headerに書いてあるやつ

FOUNDATION_EXPORT double FwVersionNumber;
FOUNDATION_EXPORT const unsigned char FwVersionString[];

Build SettingsのCurrent Project Versionがこれにあたります

Frameworkをコンパイルするとわかるんですが、 (ここでは例としてFwというframeworkをつくります)

Compile Fw_vers.c

というcのファイルが勝手に生成されてコンパイルされてます。 中身を見てみると

 extern const unsigned char FwVersionString[];
 extern const double FwVersionNumber;

 const unsigned char FwVersionString[] __attribute__ ((used)) = "@(#)PROGRAM:Fw  PROJECT:Fw-10" "\n";
 const double FwVersionNumber __attribute__ ((used)) = (double)10.;

とこんな感じでxxVersionNumberとxxVersionStringが定義されてます。

Umbrella Headerではこれらをexternしているので、Frameworkを利用しているプログラムはこれらの変数を使うことができます。

その3

Framework VersionのAとかはFrameworkの中身のフォルダ構成を見ての通りでCurrentさしているディレクトリになります

Anatomy of Framework Bundles

その4

current library versionとcompatibility versionについてですが、

Link時のビルドログをみるとわかるようにこれらの値はclangのオプションとして渡されて使われる感じになります。

clang ...  -compatibility_version 1 -current_version 1

詳しくはこの辺を読むと良いのではないでしょうか

developer.apple.com

現場からは以上です。

AutolayoutでレイアウトしたViewを外して元に戻す

InterfaceBuilderやStoryboardでAutolayoutを使って配置したViewをremoveFromSuperviewしてから、 再度addSubviewしたい!ということがたまにあります。

普通にremoveFromSuperviewしてaddSubviewすると元には戻りません。 なぜならremoveFromSuperviewした時点で親と消された子のViewのAutolayoutの制約は消えてしまうからです。

どうすればよいかというremoveFromSuperviewする前に制約をどこかに保存しておき、 addSubview後にそれらの制約を追加すればよいです。

view以下にbuttonが配置されていたのを取ったりつけたりする場合です。

private var buttonConstraints = [NSLayoutConstraint]()

...

// removeFromSuperviewするとき
view.constraints.forEach {
    if $0.firstItem is UIButton || $0.secondItem is UIButton {
        self.buttonConstraints.append($0)
    }
}
button.removeFromSuperview()

...
// addSubviewするとき
view.addSubview(button)
buttonConstraints.forEach {
    view.addConstraint($0)
}

独自のNotification名を追加

独自のNotification名を追加する場合はこんな書き方がよいような気がします。

extension Notification.Name {
    struct AppName {
        public static let DidLogin = Notification.Name(rawValue: "com.example.appname.didLogin")
    }
}

通知名の文字列には通知名の衝突を避けるために独自のprefixをつけています。

com.example.appnameのところはBundle.main.bundleIdentifierでbundle identifierをとってきて 使ったりするのがよいかもしれません。

CircleCIでipaファイルを作ろうとするとExport Failedする

ローカルではarchiveからipaにexportできるのにCircleCIだとExport Failedになる場合の対処法です。

原因としてはCircleCIの環境変数が悪さをしているようで、以下のをunsetすればなおりました

unset BUNDLE_BIN_PATH
unset BUNDLE_GEMFILE
unset BUNDLE_ORIG_PATH
unset GEM_HOME
unset GEM_PATH
unset RUBYLIB
unset RUBYOPT

面倒な場合は、このxcode-safe.shを使うと良いです。

xcode-safe.sh

https://gist.github.com/claybridges/cea5d4afd24eda268164

Visual Studio Codeをemacs keybindingにする

Visual Studio Codeを使うときにEmacs Keybindingにする方法です。

Emacs Keymap(Emacs Keymap - Visual Studio Marketplace)というのが存在するので、これを使ってみます。

  1. Shift + Command + Xを押して拡張機能を開きます
  2. 検索窓でemacsといれるとEmacs Keymapというのがでてきますので、インストールを押せばOKです。

Macで出たエラー

MacC-x C-fでファイルを開こうとすると以下のようなエラーがでました

command 'workbench.action.files.openFolder' not found

↓を見た感じ、Macだと'openFileFolder'じゃなきゃアカン的なことが書いてありました・・

bind command(workbench.action.files.openFile) failed · Issue #5437 · Microsoft/vscode · GitHub

メニューから Code->基本設定->キーボード ショートカット を選択し、 以下のようにキーバインドを上書きするとなおりました。

// 既定値を上書きするには、このファイル内にキー バインドを挿入します
[
    {
        "key": "ctrl+x ctrl+f",
        "command": "workbench.action.files.openFileFolder"
    }
]

SwiftでKVOするときはObjective-Cのプロパティ名を使う

当たり前といえば当たり前なんですが、 iOSのKVO(addObserver(_:forKeyPath:options:context:))はObjective-CのNSObjectのメソッドなので、指定するkeyPathはObjective-Cのプロパティ名じゃないとだめです。

例えばUIViewのisHiddenをKVOしたい場合は↓のように、isHiddenではなくhiddenになります。

// Swift3以前
view.addObserver(self, forKeyPath: "hidden", options: [], context: nil)
// Swift3以降
view.addObserver(self, forKeyPath: #keyPath(UISwitch.hidden), options: [], context: nil)

iOSフレームワークのクラスに対してKVOを使う際は、補完に頼るとミスることがあるので、Objective-Cの仕様も確認しましょう (#keyPathがもうちょい賢ければ・・)

addObserver(_:forKeyPath:options:context:) - NSObject | Apple Developer Documentation

TwitterKitを使ってログインするときの注意点

TwitterKitを使ってログインするときの注意点を2点

1. Twitterアカウントを登録しているかで挙動が変わる

TwitterKitを使ってTwitterログインするときに、TwitterのアカウントをiOSに設定しているかどうかで挙動が変わります。 (2017年1月時点)

iOSの設定でTwitterアカウントを登録している場合

iOSTwitterアカウントを登録済の状態でTwitterログインをすると、許可のポップアップが最初に表示されます。

ここで許可すると、Twitterログインが成功します。(特に画面遷移が発生しない)

iOSの設定でTwitterアカウントを登録していない場合

SafariViewControllerが立ち上がり、Twitterログイン画面が表示されます。


といった風に遷移が変わってくるので、動作確認する場合は、2パターンは確認する必要があります。

動作確認時に発生した不具合

Twitterアカウントを登録していない場合はSafariViewControllerが立ち上がりますが、以下のようなエラーがコンソールに表示されてSafariViewControllerが立ち上がらないことがあります。

Desktop applications only support the oauth_callback value 'oob'

対応方法としては、 Twitter Application Management で、callback URLになにかしらURLを設定すると治ります。

2. Info.plistにconsumer keyとsecretが書かれる

Fabricを使ってTwitterKitを取り込むとInfo.plistにconsumer keyとconsumer secretが書かれます。

アプリ側で保持している以上どこに書いても問題はあるとは思うのですが、Info.plistに書くよりはコード上に書いたほうが幾分ましかなと思います。

コードでConsumerKeyを設定する

このようにFabric.with([Twitter.self])より前にstartWithConsumerKeyを呼んでやらないとクラッシュします。

import TwitterKit

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        ...
        Twitter.sharedInstance().start(withConsumerKey: "xxx", consumerSecret: "yyy")
        Fabric.with([Twitter.self])
        return true
    }