DRYな備忘録

Don't Repeat Yourself.

Googleスプレッドシートで値の抽選

問題

こういうリストがあって、ここからランダムに値を抽出したい。

解決

= INDEX(A:A, RANDBETWEEN(1, COUNTA(A:A)), 1)

おしまい

解説

  • INDEX: 値を参照、行、列を指定して取得する
  • RANDBETWEEN: 2つの整数の間の数をランダムに返す
  • COUNTA: 参照における値のあるセルの数を返す
= INDEX(
    A:A, '← INDECの第1引数(参照)候補値の一覧。今回はA列全体
    RANDBETWEEN(
        1, '← 1行めから抽出したいので1
        COUNTA(A:A) '← 値があるぶんだけ抽出したいのでCOUNTA
    ),  '← INDEXの第2引数(行)RANDBETWEENの出力が入る
    1  '← INDEXの第3引数(列)今回はA列固定なので1
)

注意

  • それぞれのセルが独立してRANDを計算するので、複数抽出した場合に重複を許します
  • COUNTAが値の歯抜けに対して弱いという話をどっかで見ましたが、とりあえず動いたんで検証してません 🤪

DRYな備忘録として

【iOS】ビルドバージョンの自動インクリメントのSwift実装(PlistBuddyからの脱却)

背景

Xcodeのプロジェクトをやっていて、CI/CDなどを整えていると、iOSプロジェクトのビルドバージョン( 1.0 (N) のNの部分)を自動でインクリメントとかしたくなることがある。

PlistBuddyを使え

macOSだとPlistBuddyというコマンドが/usr/libexec/PlistBuddyにあるので、それを使えばよい。

% /usr/libexec/PlistBuddy
Usage: PlistBuddy [-cxh] <file.plist>
    -c "<command>" execute command, otherwise run in interactive mode
    -x output will be in the form of an xml plist where appropriate
    -h print the complete help info, with command guide

%
% /usr/libexec/PlistBuddy -c "Set :CFBundleVersion 2" ./BuildVersionExample/Info.plist
%

Info.plist に以下のような変化がある。

f:id:otiai10:20200312204540p:plain

PlistBuddyはなにをやってるの

このコマンドによって生成されたdiffがこちら

f:id:otiai10:20200312204632p:plain

Info.plistというXMLなファイルのプロパティがちょっと変わってるだけですね。

ゴール

CI/CDマシンはmacOSである前提はありつつも、ローカルマシンの実行ファイルに依存するのはなんだか気に食わないので、ひとまずこの限られた機能だけSwiftで実装できないかな、というのがゴール。せっかくなので、CocoaPodsに公開とかもしてみたいです。

技術概観

以下の技術課題に分解されるかと思う。

  1. マシンにビルドインなswiftないしswiftcを使ってCLIツールは作れるか?
  2. Swiftコードから、XML、とくにplistのread/writeは可能か?
  3. インクリメント(できるだろナメてんのか)
  4. CocoaPodsへの公開は(カジュアルに)できるのか?
  5. 検証: pod install で自作のpodを落としてきて使えるか

1. Swiftを使った簡単なCLIの実装

参考にしたもの

どうやら --type=executable というのを使えば1発っぽいな

% swift --version
Apple Swift version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)
Target: x86_64-apple-darwin18.7.0
% mkdir -p ~/proj/swift/SwiftExampleCommand
% cd ~/proj/swift/SwiftExampleCommand
% git init
% swift package init --type=executable
Creating executable package: SwiftExampleCommand
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/SwiftExampleCommand/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/SwiftExampleCommandTests/
Creating Tests/SwiftExampleCommandTests/SwiftExampleCommandTests.swift
Creating Tests/SwiftExampleCommandTests/XCTestManifests.swift

f:id:otiai10:20200312211830p:plain

ほーう。main.swiftや、Testsも自動で生成されているのがわかる。

とりあえず

% swift build
[3/3] Linking SwiftExampleCommand

とすると、.buildディレクトリが生成されており、

f:id:otiai10:20200312212331p:plain

うーん、かんたん。いちおうmain.swiftを覗いて、ちょっと細工をし、いちいちbuildすんのめんどいなということで適当にrunとか試してみると

f:id:otiai10:20200312212849p:plain

うーん、かわいいやんけ。

2. SwiftコードからXMLのread/write

参考にしたもの

FoundationからPropertyListSerializationというクラスが利用できるが、とりあえずXMLParserで具合を確認してみる。

github.com

どうやらXMLParserっていうのはXML<>といったトークンをパースして、各要素をwalkthroughするprotocolを提供しているようで、delegateXMLDocumentなどを使って、最終的にxmlStringなどを取得してfileに書き込めばいいと思う。

が、めんどくさいことこの上ないので、PropertyListSerializationを使ってみます。

f:id:otiai10:20200313023139p:plain

github.com

ばっちり動いたやんけ。コードもすっきり。

その他の参考:

3. インクリメント

しらんけど、上記でNSMutableObjectで取ってるのでValueにAnyが来ており、これをIntとしてインクリして返すのがよいかと思われる。

参考にしたもの

こんな感じで

plist[propKey] = {(currentVersion: Any?) -> Int in
    if let v: Int = currentVersion as? Int {
        return v + 1
    }
    return 1
}(plist[propKey])

github.com

4. CocoaPodsへの公開

参考にしたもの

その他、実装上参考にしたもの

成果物

github.com

ほんで、

% pod trunk register otiai10@gmail.com "Hiromu Ochiai" --description='my macbook pro'
% pod trunk push MiniBuddy.podspec

f:id:otiai10:20200314034524p:plain

かんたんだった

pod installで自作のpodを落としてきて使う

こういうPodfile書いてpod installしたらちゃんと落ちてきた

target 'MiniBuddyExample' do
  use_frameworks!
  pod 'MiniBuddy'
end

だけど、以下の問題がある

  • 実行ファイルがビルドされてtrunkにpushされるかと思ったがそうではなく、ばっちりソースしか落ちてきてない
    • [追記] これは、spec.source_filesに配布したいものだけを含めることで治った
  • ちゃんとLICENSEファイル配置してるんだけど[!] Unable to read the license file LICENSE for the spec MiniBuddy (0.1.0)のエラーが出る

雑感

  • エジプトも地球でした

DRYな備忘録として

【Xcode】/usr/lib/swift/libswiftCore.dylib: mach-o, but not built for iOS simulator

問題

UITest実行時に、以下のエラーが出てテストがコケる。

2020-02-26 15:34:47.656498+0200 ExampleUITests-Runner[2134:4937234] +[CATransaction synchronize] called within transaction
2020-02-26 15:34:47.665527+0200 ExampleUITests-Runner[2134:4937234] Running tests...
2020-02-26 15:34:47.690928+0200 ExampleUITests-Runner[2134:4937234] The bundle “ExampleUITests” couldn’t be loaded because it is damaged or missing necessary resources. Try reinstalling the bundle.
2020-02-26 15:34:47.691036+0200 ExampleUITests-Runner[2134:4937234] (dlopen_preflight(/Users/hiromu/Library/Developer/Xcode/DerivedData/Example-cfyafhhbbnwfizglyzbfwoouudes/Build/Products/Debug-iphonesimulator/ExampleUITests-Runner.app/PlugIns/ExampleUITests.xctest/ExampleUITests): Library not loaded: /usr/lib/swift/libswiftCore.dylib
  Referenced from: /Users/hiromu/Library/Developer/Xcode/DerivedData/Example-cfyafhhbbnwfizglyzbfwoouudes/Build/Products/Debug-iphonesimulator/ExampleUITests-Runner.app/PlugIns/ExampleUITests.xctest/ExampleUITests
  Reason: no suitable image found.  Did find:
    /usr/lib/swift/libswiftCore.dylib: mach-o, but not built for iOS simulator)

環境

調査

$ ls -la /usr/lib/swift/libswiftCore.dylib
-rwxr-xr-x  1 root  wheel  6698944  7 29  2019 /usr/lib/swift/libswiftCore.dylib

dylibは、たしかに有る。but not built for iOS simulatorとあるので、UITestのために使用しているiOS simulatorのバージョンを変えればいいのだろうか?プロジェクトのビルドターゲットがiOS 12.0+なので、12.0などを使っていたが、いろいろ試して

通った。

とりあえず結論

なので、プロジェクトのビルドターゲットに関わらず、使っているXcodeがサポートしているiOSのバージョンのSimulatorのlibswiftCore.dylibしかないよ、ということなのだと思う。さもありなん、という感じ。

Xcode 11 で iOS 12.0 上のUITestをしたい

// TODO: こんどかく

NOT DRY YET

【iPhone】No such module 'RxSwift'

問題

pod installRxSwiftをインポートしたつもりだが、ViewControllerでimport RxSwiftとすると、

No such module 'RxSwift'

f:id:otiai10:20200223203143p:plain

となり、もちろんビルドもRunもできない。

解決

.xcodeprojではなく、.xcworkspaceから開く。

自分の場合は、ターミナルからopen MyTestProject.xcworkspaceと打って開いた。

雑感

  • エジプト、メシが不味い

DRY

【iPhone】【Safari】getUserMediaで取ったMediaStreamをvideoタグにセットしても最初のフレームだけ描画されて止まってしまう問題【React】

問題

  • iPhoneSafariにおいて、
  • navigator.mediaDevices.getUserMediaによって、
  • videoを含むMediaStreamを取得し、
  • HTMLのvideoタグに動画を描画しようとすると、
  • 最初のフレームだけ描画されたのち、フリーズしてしまう。

解決

getUserMediaを呼ぶ前に、videoタグにautoplay, muted, playsinline属性が付与されている必要がある。

stackoverflow.com

その他の注意

  • Reactのjsxないしtsxファイルにおいて、<video playsinline={true} />とした場合でも、最終的にレンダリングされたHTMLにその属性が付与されているか確認すること
    • されてなかったので、わざわざRefObjectつくってDOMのAPIからsetAttribute('playsinline', '')などとした
  • そもそもgetUserMediasecure context と呼ばれるウェブページにおいてのみ動作するため、HTTPSを使ったページにホストする必要がある

参考

雑感

  • 1日ハマった

DRYな備忘録として

iOSでFontAwesomeを使う【Xcode10.3】

Xcodeは変化が早いので日付に注意してください

ゴール

  • XcodeiOSプロジェクトでFontAwesomeが使える

参考

要素

  1. FontAwesomeのOTFファイルを入手する
  2. FontAwesomeのOTFファイルをXcodeプロジェクトに追加する
  3. 使ってみる

FontAwesomeのOTFファイルを入手する

fontawesome.com

今回欲しいのはOTFファイルなので、for DesktopのほうをDownloadします。

f:id:otiai10:20190808140812p:plain
ダウンロードして解凍したもの

FontAwesomeのOTFファイルをXcodeプロジェクトに追加する

ここに懇切丁寧に解説があるが、 Adding a Custom Font to Your App | Apple Developer Documentation

要素としては、

  1. 今後のためにfontsGroupをつくっておく
  2. ハードコピーを作成しつつotfファイルを追加する
  3. info.plistで、Fonts provided by applicationを追加する

f:id:otiai10:20190808141402p:plain
info.plist における Fonts provided by application の item の名前は、ファイル名から拡張子`.otf`を除いたものとした

使ってみる

f:id:otiai10:20190808142827p:plain

雑感

  • 意外とかんたんだった

DRYな備忘録として

【iOS】ボタン長押しの実装【Xcode10.3】【Swift5】

Xcodeは変化が早いので記事の日付に注意してください

ゴール

  • 長押ししてアラートとか出るボタンを設置する

tl;dr

override func viewDidLoad() {
    super.viewDidLoad()
    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongPress))
    self.button.addGestureRecognizer(recognizer)
}

@objc func onLongPress() {
    let alert = UIAlertController(title: "Long Press", message: "親父にも長押しされたことないのに!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

以下読まなくていいです。

1. ボタンの設置・追加

最近のXcodeはオブジェクトの追加をするペインが無い。Cmd + Shif + Lで、かつてオブジェクト選択のペインだったものがダイアログとして出てくる。

f:id:otiai10:20190808105440p:plain
Cmd + Shift + L

ここで、ButtonをStoryboardにドラッグアンドドロップして、適当にテキスト変えて、終了。

f:id:otiai10:20190808105756p:plain

2. 通常のTouchイベントの登録

Storyboardを表示した状態で、右上の「◯◯」を押すと、Storyboardに紐付いているViewControllerが反面に開かれる。

f:id:otiai10:20190808110042p:plain

で、ButtonとViewControllerを紐付ける。

f:id:otiai10:20190808110925p:plain

ちょっとAlert出すコード書く。

@IBAction func onTouchDownButton(_ sender: Any) {
    let alert = UIAlertController(title: "Touch Down", message: "押したねっ", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "close", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

こうなる

f:id:otiai10:20190808112127g:plain

3. 長押しのRecognizerを設置

とりあえずさっき作って紐付けたTouch Downイベントは消して、以下のようなコードを書く。

このとき、self.buttonは、上記のような要領で、Ctrl押しながらUIパーツをViewControllerにひっぱってきて置いたもの。

override func viewDidLoad() {
    super.viewDidLoad()
    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(onLongPress))
    self.button.addGestureRecognizer(recognizer)
}

// func onLongPress(_ sender: UILongPressGestureRecognizer) {
@objc func onLongPress() {
    let alert = UIAlertController(title: "Long Press", message: "親父にも長押しされたことないのに!", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
    self.present(alert, animated: true, completion: nil)
}

こうなる

f:id:otiai10:20190808114936g:plain

雑感

  • Xcodeは補完がきもちいいですね
  • Swiftはguardが好きです
  • iOS開発たのしい

DRYな備忘録として