- 投稿日:2020-09-18T22:04:58+09:00
Swift のOptionalをUnwrapする5つの方法 4個目はお勧め
- 投稿日:2020-09-18T21:44:54+09:00
【iOS】AssetCatalogのColorSetを閲覧できるツールを作ったお話
Xcode9 iOS11からAssetCatalogで色も管理できるようになりました。大分前に導入された仕組みなので既に活用されているチームも多いかと思います。
単色の管理はもちろんですが、1つのColorNameに対してAny, Light, Darkの色を紐付けることができ、StoryBoard上ではColorNameを指定するだけでLight mode, Dark modeを自動で判別し色を切り替えてくれるのでダークモードに対応する際に効果を発揮してくれます。しかし、同一のUIに対して複数の色が入り込んでくると「ColorNameがLight時は、このカラーコードで、Dark時はこのカラーコード...」などと混乱する場面がありました。
また、デザイナーさんはXcode側のAsset Catalogを直接見れるわけではないので現在リリースされているアプリ内で利用できるカラーコードリストを別で管理する手間などもあり課題を感じることが多かったです。そんなことがあり、最新のプロジェクトに含まれるAssetCatalogを閲覧できるような仕組みがあれば、エンジニアもデザイナーも楽出来るんじゃないと思ったので...
とりあえず作ってみた。AssetCatalogの中身
例えばこんなColorSetがあったとします。
実際は、下記のようなjsonで管理されておりXcode側がGUIで上手いこと表示しているに過ぎません。contents.json{ "colors" : [ { "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "0.000", "green" : "0.149", "red" : "1.000" } }, "idiom" : "universal" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "light" } ], "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "1.000", "green" : "0.198", "red" : "0.017" } }, "idiom" : "universal" }, { "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ], "color" : { "color-space" : "srgb", "components" : { "alpha" : "1.000", "blue" : "0.000", "green" : "0.977", "red" : "0.000" } }, "idiom" : "universal" } ], "info" : { "author" : "xcode", "version" : 1 } }上手いことContents.jsonをディレクトリから探し出して、Any・Light・Darkそれぞれのカラーコードを取得することが出来れば必要なデータは抽出することが出来ます。
AssetCatalogViewer
MasamiYamate/AssetCatalogViewer
Githubに上がっているリポジトリからAssetCatalogを探し、各ColorSetのContents.jsonからAny・Light・Darkのそれぞれのカラーコードを取得してViewer向けのデータに整形する簡単なお仕事をしてくれるツールを作りました。
jenkinsやBitriseなどCIと組み合わせて利用することを考えていたので、Githubに上がっているプロジェクトを対象にしています。動作確認環境
nodejs v14.10.1
利用方法
1.
npm install
を行って依存モジュールをInstallします。$ npm install2.AssetCatalogを閲覧したいリポジトリのURLを設定します
$ npm run setup > colorsetjsonparser@1.0.0 setup /Users/hogehoge/Documents/Workspace/AssetCatalogViewer > bash ./deploy-script/setup.sh Enter the clone url or ssh: 【github url or ssh】 audited 1312 packages in 4.365s 56 packages are looking for funding run `npm fund` for details found 0 vulnerabilities Repository setting completed!!3.masterブランチに適応されているColorSetを取得しViewを下記コマンドで生成します。
$ npm run build Success!!以上で完了です。
生成されたViewerページは、distディレクトリ配下にあります。
./dist/index.html
を開くとこんな感じで表示されます。
現段階では、ColorSetの名称とAny・Dark・Lightそれぞれのカラーコードを横並びで表示するだけです。
クリックしたらUIColorのコードが取得出来たりするともっといいのかなと思いますが、
とりあえずAssetCatalogをXcode上で開かずに簡単に見れるということは実現出来たかなと思っています。文字色が黒固定なので、背景色によっては見辛いものがあったりしますが、今後対応してく予定です。
最後に
結構強引な手段ではありますが、目的を達成できそうなものは作れたかなと思います。
チームへの導入はまだ出来ていないので、エンジニア・デザイナーそれぞれからフィードバックをもらいつつさらにブラッシュアップをかけていきたいと考えています。
(とりあえず実現からだったので、実装は荒いです。改修に併せて、リファクタも進めます。)このようなアプローチでエンジニア・デザイナー間のやりとりを楽にしようと思っているという1つの考えとして参考になれば幸いです。
- 投稿日:2020-09-18T21:42:20+09:00
クロージャと、@escapingと、循環参照。
クロージャ
使い方
変数
や引数
に、関数の処理を直接代入する。使う理由
最近のプログラミング言語では、
引数に関数を入れるとか、戻り値に関数を入れるとかが当たり前になっているので、
そういうときにクロージャを使うと綺麗に記述できるので、みんな嬉しい。
引数
として使うパターンが多い名前の由来
コード
let, varに、関数の処理を直接代入
// MARK: - let, varに、関数の処理を直接代入 // 定数closureが、恰も関数のように扱える let closure = { () -> () in print("Hello World!")} closure() let closure_2 = { () -> Void in print("hamburger")} closure_2() // 戻り値がVoid型、引数がない場合は、(引数) -> 戻り値の型 in を省略可 let closure_3 = { print("Yeah!") } closure_3() // 型を指定 let closure_4: (Int, Int) -> Void = { (num1: Int, num2: Int) -> Void in print(num1 + num2) } // 型推論ってやつ let closure_5 = { (num1: Int, num2: Int) -> Void in print(num1 + num2) } closure_4(100, 199) closure_5(100, 199) // 因みに、closure_4 は num1, num2を省略可。 // 内部引数名を省略 -> 「$」を使用。 // $0 は最初の引数を表し、$1 は2番目の引数を表します。 let closure_6: (Int, Int, Int, Int) -> Void = { print ($0 - $1 + $2 + $3) } closure_6(30, 20, 100, 10) // 120型推論とは?
型の指定をしなくても、代入した値に応じて値の型を推論してくれる機能。
つまり、Swiftは変数の宣言時に型の指定を省略できる。( -> むしろ推奨。)内部引数とは?
関数を呼び出すときは、
外部引数名
を利用します。普段 私たちは、
内部引数名
を外部引数名
として、関数を呼び出しています。
違いはこちら
クロージャを引数として、関数を実行
引数
として使うパターンが多い// MARK: - クロージャを引数として、関数を実行 func closureTest(num1: Int, num2: Int, closure: (Int, Int) -> Int) { print(closure(num1, num2)) } // return文が1行のみの場合には「return」は省略可 closureTest(num1: 300, num2: 5000, closure: { (num1, num2) -> Int in return num1 + num2 }) closureTest(num1: 300, num2: 5000, closure: { (num1, num2) -> Int in num1 + num2 }) // 引数の型を入力しないと、こうなるので注意。 // closureTest(<#T##<<error type>>#>, <#T##<<error type>>#>, <#T##<<error type>>#>) // トレーリング クロージャ (= Trailing Closure) closureTest(num1: 100, num2: 400) { (num1, num2) -> Int in num1 + num2 // return省略 }Trailing Closureとは?
関数の引数のうち 最後の引数がクロージャの場合、
クロージャを( )
の外に書くことができる。func testprint(str1: String, closure: (String) -> Void) { closure("僕の名前は\(str1)です。") } // トレーリングクロージャの場合 -> 美しい testprint(str1: "玄邪 太郎") { string in print(string) } // 通常のクロージャの場合 -> 可読性が悪い... testprint(str1: "玄邪 太郎",{ string in print(string) })
- クロージャが引数だと、
{ }
の外に( )
を包まないといけないので、 可読性が悪くなる。(-> そこで、Trailing Closureが考案)クロージャの、基本的な性質2つ
- ?クロージャーは関数の
引数
や変数
として使える- ?クロージャーは自分が定義されたスコープを
キャプチャ
する引数としてのクロージャ、利点?
- コードが綺麗になる。
- 呼び出し側で、処理が記述できる。
呼び出され側は、クロージャに引数として値を渡し、
「値は渡すから、その値はそちらで好きに料理してね」という具合です。class Foo { let val:Int = 10 func testClosure(closure: (Int) -> Void) { closure(self.val) // self } } class Bar { let foo = Foo() // 引数の値を倍にする func twice() { foo.testClosure{ arg in print(arg * 2) } // arg = argument = 引数 } // 引数の値を半分にする func half() { foo.testClosure{ arg in print(arg / 2) } } } let bar = Bar() bar.twice() // 20 bar.half() // 5クロージャによる、 変数と定数のキャプチャ?
ローカルスコープ(=
scope_1
)で定義された変数や定数は、
ローカルスコープ内でしか使用できませんが、(=scope_2
では使用不可)クロージャが参照している変数や定数は、
クロージャが実行されるスコープ(=scope_4
)が
変数や定数が定義されたローカルスコープ以外(=scope_3以外
)であっても、
クロージャの実行時に使用できます。これは、クロージャが 自身の定義されたスコープ(=
scope_3
) の
変数や定数への参照を保持している為で、この機能をキャプチャ
と言います。class Foo { // scope_2関数は、普通の関数scope_1を2回実行しています。 func scope_1() { var toto = 1 toto += 1 // toto = toto + 1 と同義 (= 変数totoの値を更新) print(toto) } func scope_2() { scope_1() scope_1() } // scope_4関数は、scope_3関数から返されたクロージャを2回実行しています。 func scope_3() -> () -> Void { var tete = 10 let closure = { tete += 1 print(tete) } return closure } func scope_4() { let tutu = self.scope_3() tutu() tutu() } } let test = Foo() test.scope_2() // 2, 2 test.scope_4() // 11, 12// scope_2関数は、普通の関数scope_1を2回実行しています。
普通の関数は、実行後はリセットされるので、
1回目の実行で「2」と表示されても、実行後ローカル変数toto
の値は「1」に戻ります。なので2回目の実行でも「2」と表示されます。
// scope_4関数は、scope_3関数から返されたクロージャを2回実行しています。
クロージャが、
scope_3
関数のローカル変数tete
を参照しているので、
1回目の実行で「11」、2回目の実行では1回目の参照を保持しているので「12」となります。
キャプチャとは?
自身の定義されたスコープの変数や定数への、参照を「保持」する機能。
クロージャ特有の機能。クロージャにて、self を使う理由
明示的な
self
には、循環参照 が存在しないよう確認を促す役割がある。(Escaping Closuresは)循環参照を起こす危険性があるので、
(プログラマにそれを意識させるために)クロージャーの中で親スコープを強参照するときは、selfをつける。
self
を付けることが、循環参照を防ぐことに直結しているわけではなくて、
コードの意図が明確になり、循環参照しないかにプログラマが意識を向けて書くようになる。
@escaping
@escaping
とは?
- Swiftの 属性(= attribute)の1つ
@escaping
属性は、クロージャに対して指定する追加情報。- 関数に引数として渡されたクロージャが、関数のスコープ外で保持される可能性があることを示す。
var array = [()->Void]() func testEscaping(arg: @escaping ()->Void) { //関数のスコープ外の配列に追加 //クロージャが関数外で保持されることになるので、@escaping属性が必要 array.append(arg) } testEscaping {print("玄邪 一郎")} testEscaping {print("玄邪 二郎")} array.forEach { $0() } //Swiftではクロージャの引数に自動的に$0、$1、$2…と順に名前が付与されます。属性(= attribute)とは?
- コンパイラに対し、宣言や型の補足情報を伝えるもの。
@
を使う。
@escaping
を、 使う理由複数のタスクを、非同期で並列処理させるため。
どんな時に、 必要か
- クロージャが、スコープ外で
強参照
されるとき (= プロパティとして保持されるとき)- クロージャを、
非同期
で実行するとき (= メソッド内ですぐに実行されないとき)クロージャをすぐに実行し、どこからも強参照されない場合は、
@escaping
は必要ありません。よくある例
非同期処理をする、完了ハンドラとしてのクロージャ。
// NG: コンパイルエラー func someAsyncMethod(completion: () -> Void) { DispatchQueue.main.async { completion() } } // OK func someAsyncMethod(completion: @escaping () -> Void) { DispatchQueue.main.async { completion() } } // completionHandler -> クエリが完了したときに実行される // DispatchQueue -> 処理待ちタスクを追加するためのキュー「循環参照」 に 気を付けよう?
@escaping
なクロージャは、どこか から強参照される可能性があります。
その参照元をクロージャ内で強参照
すると、循環参照になります...循環参照
循環参照とは?
お互いにインスタンスを参照しあうため、
どちらも解放されずにそのまま残り続けてしまう現象。? (ぐるぐる...)なぜNG?
生成したインスタンスが メモリから解放されないと、
メモリ リーク
となるから。
「解放」 = "ここ使い終わったから どうぞ"?メモリ リーク (= Memory leak)
永久にメモリを消費し続ける現象。
再現性が低く、テストもデバッグも極めて困難な悪質なバグとして有名。class Sample {} // Sample クラスのインスタンスが生成されるが参照カウンタが 0 のままなのですぐに解放される Sample() // Sample クラスのインスタンスが生成されて sample に代入されているので参照カウンタが 1 となり解放されない var sample: Sample? = Sample() // nil を代入すると参照がなくなるのでインスタンスの参照カウンタが 0 になり解放される sample = nil参照カウンタ
TODO: 後述
循環参照 を 防ぐには?
弱参照
をする。
weak または unowned という修飾子を使う。(unknownedではない...!)un owned: 所有されていない
un known: 知られていない, 不明な
un known ed -> こんな言葉ない
weak
とunowned
の違いTODO: 後述
なぜweakが、 循環参照を防げるのか?
TODO: 後述
[ ] とは何か
TODO: 後述
普段なにげなく書いている[unowned self]の意味を調べる
参照カウントについて
これもおしまい
- 投稿日:2020-09-18T21:26:46+09:00
iOS14で「バックグラウンド再生」が出来ない不具合への対処
概要
現在、リリースしているメディア管理系アプリで行ったiOS14に関する不具合調査とその対策です。
経緯とバグ調査
アプリを使って頂いているユーザーさんから「iOS14でバックグラウンド再生が出来ない」
との問い合わせがありました。
早速、手元の実機で調査してみると
動画ファイルを再生中 → アプリをバックグラウンドへ移行 → 再生が一時停止されてしまう
という現象を確認出来ました。
これが、iOS14の仕様(であればアプリ側のバグ)なのか
iOS14側の不具合なのかは今のところ分かりません。
ただ、音声ファイルの再生時にこの現象は起きないので
(バックグラウンドでも再生は継続される)おそらく、iOS14の新しい仕様の可能性が高いのかなと思います。
ちなみに一時停止してしまった動画は、
コントロールセンターからプレイバックボタンを押す事で、再び再生が可能でした。
対策
バックグラウンドで動画再生させるケースはあまりない?と思われがちだが
実際、弊アプリのユーザーさんでは音楽動画などをBG再生して聴かれるケースも多いです。
症状は動画ファイル再生時のみですが、取り急ぎでも対応が必要です。
まずAVAudioSessionで色々やってみる
AVFoundation FrameworkのAVAudioSessionのカテゴリーを調整してみます。
AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback, options: [])ここのmodeとoptionsを調整してみます。
extension AVAudioSession.Mode { public static let `default`: AVAudioSession.Mode public static let voiceChat: AVAudioSession.Mode public static let gameChat: AVAudioSession.Mode public static let videoRecording: AVAudioSession.Mode public static let measurement: AVAudioSession.Mode public static let moviePlayback: AVAudioSession.Mode public static let videoChat: AVAudioSession.Mode public static let spokenAudio: AVAudioSession.Mode public static let voicePrompt: AVAudioSession.Mode }public struct CategoryOptions : OptionSet { public init(rawValue: UInt) public static var mixWithOthers: AVAudioSession.CategoryOptions { get } public static var duckOthers: AVAudioSession.CategoryOptions { get } public static var allowBluetooth: AVAudioSession.CategoryOptions { get } public static var defaultToSpeaker: AVAudioSession.CategoryOptions { get } public static var interruptSpokenAudioAndMixWithOthers: AVAudioSession.CategoryOptions { get } public static var allowBluetoothA2DP: AVAudioSession.CategoryOptions { get } public static var allowAirPlay: AVAudioSession.CategoryOptions { get } }色々と組み合わせてみるが、効果無し。
仕方がないので、少し強引な対策で対処
根本的な対策方法については、引き続き調べていくとして
とりあえずの落としどころが欲しかったので、
まずAVPlayerItemのstatusを監視
システムから再生が停止されてしまった際 → playbackLikelyToKeepUpの変更を検知
isPlaybackLikelyToKeepUpそのタイミングでバックグラウンド再生中であれば、再び動画を再生させる。
と言う方法で対処することにしました。
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?){ if let item = object as? AVPlayerItem, let keyPath = keyPath { if item == self.playerItem { switch keyPath { // ...略 case #keyPath(AVPlayerItem.playbackLikelyToKeepUp): debugPrint("PlaybackLikelyToKeepUp is changed") // ** BG状態かつ、再生ステータスなのにAVPlayerが停止という状態をチェック if isInBackground, isInconsistentPlaybackState { play() debugPrint("Background Playback Forced Resuming >> Did Execute") } default: break } } } }調査は継続しつつ、これで様子を見ていきたいと思います。
- 投稿日:2020-09-18T21:19:37+09:00
【Swift5】 iOSの音声合成(AVSpeechSynthesizer)の声を男性に変更する
概要
iOSでは標準で利用可能な音声合成(AVSpeechSynthesizer)が用意されている。
普通に利用する場合、女性の声が利用可能だが、声を男性に変更する方法をメモする。AVSpeechSynthesizerの基本的な使い方については以下の記事を参照。
https://qiita.com/maKunugi/items/dc9da201a663c8773c8c音声の変更
let voices = AVSpeechSynthesisVoice.speechVoices()利用可能な声は、AVSpeechSynthesisVoiceクラスのspeechVoicesメソッドから取得が可能。
voiceの中身は下記のような内容。
name: Kyoko gender(rawvalue): 0 lang: ja-JP identifier: com.apple.ttsbundle.Kyoko-compactspeechVoicesメソッドから利用したい声を取得したら、AVSpeechUtteranceクラスにsetしてあげればOK。
let synthesizer = AVSpeechSynthesizer() ... let utterance = AVSpeechUtterance(string:text) utterance.pitchMultiplier = 1.0 utterance.voice = voice synthesizer.speak(utterance)利用可能な日本語音声
手元の端末で確認できた日本語の音声はこちら。
- Otoya (Enhanced) #男性 ja-JP com.apple.ttsbundle.Otoya-premium - Hattori #男性 ja-JP com.apple.ttsbundle.siri_male_ja-JP_compact - Kyoko #女性 (Default) ja-JP com.apple.ttsbundle.Kyoko-compact - O-ren #女性 ja-JP com.apple.ttsbundle.siri_female_ja-JP_compact - Otoya #男性 ja-JP com.apple.ttsbundle.Otoya-compact上記の音声が確認できました。男性の声も2種類指定が可能なので、「Hattori」もしくは「Otoya」の音声を指定してあげれば男性の声で読み上げをすることが可能です。
注意事項
音声が端末で利用できるかの確認
何もしない場合はDefaultの女性の声(Kyoko)が読み上げに利用されます。
端末内にない存在しない音声を利用しようとするとクラッシュします。
そのため、端末で利用したい音声が存在しているかは必ず確認する必要があります。var voice:AVSpeechSynthesisVoice? = voices.first{ $0.identifier == "com.apple.ttsbundle.siri_male_ja-JP_compact" } if let voice = voice { ~~~~~ } else { ~~~~~ }ユーザが音声をダウンロードすることで利用可能な音声が増える
初期状態ではほとんどの端末がDefaultの音声しか利用ができない。(シミュレータは増やせない模様)
Siriの設定やアクセシビリティの設定等で音声の変更を行うと、追加の音声データを端末にダウンロードができる。その手順を踏んだ端末には上述したような複数の音声が利用可能になる。
そのため、必ず男性の声でなければならないといった制約のあるアプリを提供したい際は、男性の音声データが端末にダウンロードされている状態にするため、うまくユーザを誘導する必要がある。
- 投稿日:2020-09-18T19:02:00+09:00
react-native-firebase(v6↑) + react-native-push-notificationを使ったプッシュ通知実装の注意点
まえがき
私が開発しているReact Nativeアプリについて、元々react-native-firebase(v5)を使ってプッシュ通知を実装していましたが、そろそろバージョンを上げることにしました。
ところが、v6からローカル通知の機能がNotifeeという有料の別ライブラリに分離されており、ローカル通知の機能のみ他のライブラリを採用せざるを得なくなりました。
そこで使えそうなライブラリを調べてみたところ、
- Notifee: ローカル通知専用ライブラリ。react-native-firebaseのチームが作っており当然相性も良く、品質も高そうだが、有料。
- react-native-push-notification: リモート&ローカル通知ライブラリ。一番スターが多い。
- react-native-notifications: リモート&ローカル通知ライブラリ。Wix製。Androidでchannelを指定してローカル通知を送れなさそう。(今のところ)
という感じのようだったので、react-native-push-notificationを採用することにしました。
本記事では、react-native-firebase(v6↑)とreact-native-push-notificationを使ってプッシュ通知を実装するにあたっての注意点を紹介します。
実現したいこと
- FCMで受信したメッセージを、アプリの状態(Foreground/Background/Quit)にかかわらずプッシュ通知で表示したい
- プッシュ通知をタップしたとき、アプリの状態にかかわらず処理したい
本記事で触れないこと
- react-native-firebaseの導入方法
- FCMの導入方法
環境
- React Native (0.63.2)
- @react-native-firebase/app (8.4.2)
- @react-native-firebase/messaging (7.8.6)
- react-native-push-notification (5.1.1)
- @react-native-community/push-notification-ios (1.5.0)
ライブラリを導入する
※ react-native-firebaseについては導入が済んでいるものとします。
まずは、react-native-push-notificationを導入します。このライブラリは、iOS部分については@react-native-community/push-notification-iosを使用しているため、併せてインストールを行います。
npm install --save react-native-push-notification @react-native-community/push-notification npx pod-install続けて、iOSについて下記コードの追記を行います。
AppDelegate.h#import <UserNotifications/UNUserNotificationCenter.h> ... @interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>AppDelegate.m#import <UserNotifications/UserNotifications.h> #import <RNCPushNotificationIOS.h> ... - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = self; return YES; } ... - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { [RNCPushNotificationIOS didReceiveNotificationResponse:response]; completionHandler(); } - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge); }push-notification-iosのREADMEの導入手順には、他にもリモート通知用のデリゲートメソッドがいくつか記載してありますが、今回はローカル通知用として使用するため、上記のデリゲートメソッドがあればOKです。
実装前に抑えておくポイント
アプリの状態
アプリには以下の3つの状態があり、状態によってプッシュ通知受信時およびタップ時の挙動が異なります。
- Foreground: アプリが前面にあり動作している状態
- Background: ホーム画面や他のアプリが前面にあり、アプリが背面で動作している状態
- Quit: タスクキルなどでアプリが死んでいる状態
Quit時にプッシュ通知をタップした場合は、アプリが起動します。その起動したタイミングでタップしたプッシュ通知を処理することになります。
リモート通知とローカル通知
アプリがBackgroundまたはQuit状態のときにFCMを受信すると、FCM側(react-native-firebase)がプッシュ通知を表示してくれます。しかし、Foreground状態の場合には表示してくれず、代わりに
onMessage
コールバックが呼ばれます。そのため、onMessage
の中で、react-native-push-notificationを使ってプッシュ通知を表示する処理を実施する必要があります。本記事では、前者のことをリモート通知、後者のことをローカル通知と呼んでいます。実装する
関係するコードの抜粋です。
app.jsimport PushNotification from 'react-native-push-notification'; import PushNotificationIOS from '@react-native-community/push-notification-ios'; class App extends Component { constructor(props) { ... PushNotification.configure({ onNotification: notification => { // プッシュ通知をタップしたときに呼ばれる ... // URLを開くなどの処理 notification.finish(PushNotificationIOS.FetchResult.NoData); } }); } componentDidMount() { this.unsubscribeMethods = [ firebaseMessaging().onTokenRefresh((token: string) => { // トークンリフレッシュ時に呼ばれる ... // トークンリフレッシュ時の処理 }), firebaseMessaging().onMessage(message => { // Foreground時にリモートプッシュ通知を受信した際に呼ばれる this._localNotification(message); }) ]; } componentDidUnmount() { this.unsubscribeMethods(); } // ローカル通知 _localNotification(message) { PushNotification.localNotification({ title: message.notification.title, message: message.notification.body, userInfo: message.data, ... }); } }ForegroundでFCMを受信したときには
onMessage
が呼ばれるので、localNotification()
でローカル通知を行っています。プッシュ通知がタップされたときはreact-native-push-notificationの
onNotification
が呼ばれます。ローカル通知だけでなく、react-native-firebaseが表示したリモート通知がタップされたときも呼ばれます。また、Quit時にタップされてアプリが起動する場合も、起動時に呼ばれます。すなわち、プッシュ通知がタップされた際の処理は、あらゆる場合にonNotification
でカバーできることになります。なお、@react-native-firebase/messagingには、リモート通知をタップした際に呼ばれる
onNotificationOpenedApp
や、起動時に呼ぶことでQuit時にタップした通知を取得するgetInitialNotification()
が用意されていますが、リモート通知に限って処理を行いたいケースを除いては、特に使う必要はなさそうです。タップしたときの動き(メモ)
各OS、各アプリ状態、ローカル通知/リモート通知について、プッシュ通知をタップしたときにどのコールバックが呼ばれるかを整理した際のメモを載せておきます。
onNotification
では、Foregroundでタップされたかどうかの値foreground
、ユーザーがタップしたかどうかの値userInteraction
が引数で渡されますが、一部のケースで誤った値になっていそうです(該当箇所は太字で記載しています)。iOS
ローカル通知
- Foreground:
onNotification
(foreground=true, userInteraction=false)が呼ばれる- Background:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。- Quit:
onNotification
(foreground=false, userInteration=true)が呼ばれる。(getInitialNotification()
では取れない。)リモート通知
- Foreground:
onNotificationOpenedApp
が呼ばれる。onNotification
(foreground=true, userInteraction=false)も呼ばれる。- Background:
onNotificationOpenedApp
が呼ばれる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。- Quit:
getInitialNotification()
で取れる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。Android
ローカル通知
- Foreground:
onNotification
(foreground=true, userInteraction=true)が呼ばれる。- Background:
onNotification
(foreground=true, userInteraction=true)が呼ばれる。- Quit:
onNotification
(foreground=false, userInteration=true)が呼ばれる。(getInitialNotification()
では取れない。)リモート通知
- Foreground:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。onNotificationOpenedApp
も呼ばれる。- Background:
onNotification
(foreground=false, userInteraction=true)が呼ばれる。onNotificationOpenedApp
も呼ばれる。- Quit:
getInitialNotification()
で取れる。onNotification
(foreground=false, userInteraction=true)も呼ばれる。まとめ
- ForegroundでFCMを受信した際にはプッシュ通知が表示されず
onMessage
が呼ばれるので、そのタイミングでreact-native-push-notificationのlocalNotification()
を使ってローカル通知を表示する。- OS、アプリの状態、リモート通知/ローカル通知のにかかわらず、通知タップ時の処理は
onNotification
で行えばOK。onNotification
のforegroundやuserInteractionの値は怪しいので、場合分けには使わない方が無難。以上、react-native-push-notificationの
onNotification
が万能で助かったというお話でした。
- 投稿日:2020-09-18T17:58:00+09:00
xcode12でsimulatorにビルドする時失敗する問題
問題
xcode12でsimulatorにビルドする時
building for iOS Simulator, but linking in object file built for iOS, for architecture arm64
のようなエラーが出てきます。原因
https://developer.apple.com/documentation/xcode-release-notes/xcode-12-release-notes
The Build Settings editor no longer includes the Valid Architectures build setting (VALID_ARCHS), and its use is discouraged. Instead, there is a new Excluded Architectures build setting (EXCLUDED_ARCHS). If a project includes VALID_ARCHS, the setting is displayed in the User-Defined section of the Build Settings editor. (15145028)
Valid Architectures
はxcode12から削除されたのでエラーの原因になっていると思われる。解決案
project.pbxproj
内のVALID_ARCHS
を削除することでビルドができるようになると思う。
- 投稿日:2020-09-18T16:19:09+09:00
Cordova iOSアプリでvideoタグの動画をインライン再生する
↓こちらの方法がiOS10以降から使えなくなっていたので
Cordovaを使ったiOSアプリでvideoタグをインライン再生config.xml<preference name="AllowInlineMediaPlayback" value="true"/>playsinline属性を追加
<video src="sample.mp4" playsinline></video>
- 投稿日:2020-09-18T14:04:04+09:00
Xcode 12 環境でアプリがビルドできない
Xcode 12
"Xcode 12 beta 2〜3"辺りから、
Carthage/CocoaPodsを使用している
自作アプリがビルドできなくなりました。
"Xcode 12"の正式版がでても解消されていません。その原因、対策のまとめです。
各対策は、効果はありましたが、
暫定的なものか、恒久的なものかは分かりません。Xcode 12 + Carthage
Carthageビルドについてのやりとり(英語)
Carthage builds fail at xcrun lipo on Xcode 12 beta(3,4,5...)Carthageビルドエラー原因についての日本語記事
Xcode12 betaでCarthageによる依存ライブラリのビルドが失敗するCarthageビルドの対策
carthage-build.sh
リンク先のcarthage-build.sh
をプロジェクトルートに作成し、
sh carthage-build.sh --platform iOS --no-use-binaries
で実行する。Carthageバージョンエラー
Carthage実行時に、"Carthage 0.35.1"にバージョンアップエラーが出た。
brew upgrade carthage
で、"0.35.1"にバージョンアップしても、
繰り返しバージョンアップエラーが表示される。[対策]
1.brew uninstall carthage
でCarthageをアンインストール
2. https://github.com/Carthage/Carthage/releases/tag/0.35.1
からCarthage.pkgをダウンロード>インストールaws-sdk-ios + Carthage
"Xcode 12 + Carthage"環境で"aws-sdk-ios"がビルドできない。
>CocoaPodsではビルドできるため、CocoaPodsに移設する。Xcode 12 + CocoaPods
Xcodeでビルド時に、
Xcode 12, building for iOS Simulator, but linking in object file built for iOS, for architecture arm64
のビルドエラーになる。CocoaPodsビルドの対策
- 投稿日:2020-09-18T09:50:48+09:00
ScrollView のスクロール中だけ Lottie アニメーションを止めたい! (Swift)
はじめに
Lottie アニメーションを
UIScrollView
がスクロールしている最中のみ動かしたいときがあったので、その実装を紹介します。
具体的に言うと、
- スクロール中: アニメーション停止
- スクロール終了: アニメーション再開
という要求です。
準備: Lottie を動かす ViewController
ScrollView の動作関係なしに、ただ Lottie を動かす ViewController のコードを紹介します。
今回はこのコードを元に実装してみます。コメント多めに書いてますので、ある程度は追えるかと思います。
今回はなんとなくランダムな位置に複数の Lottie アニメーションを配置してみました。import UIKit import Lottie class ViewController: UIViewController { // MARK: - IBOutlet // scrollView の子要素で、 Lottie オブジェクトが配置される UIView @IBOutlet weak var contentView: UIView! // MARK: - Private properties var animations = [AnimationView]() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // ランダムな位置に 3 個の Lottie オブジェクトを生成・配置・再生する // 複数配置していることに特に意味はありません。 for _ in 1...3 { let x = CGFloat.random(in: 1.0...5.0) * 200.0 let y = CGFloat.random(in: 1.0...6.0) * 100.0 let animation = generateAnimation(x: x, y: y) animations.append(animation) contentView.addSubview(animation) animation.play() } } // MARK: - Private methods // 引数で受け取った座標を持つ Lottie オブジェクトを生成するメソッド // see: https://qiita.com/ngo275/items/c9e94bad7a7afc85e4f4 private func generateAnimation(x: CGFloat, y: CGFloat) -> AnimationView { let animationView = AnimationView(name: "sample_animation") animationView.frame = CGRect(x: x, y: y, width: view.bounds.width * 0.2, height: view.bounds.height * 0.2) animationView.loopMode = .loop animationView.contentMode = .scaleAspectFit animationView.animationSpeed = 1 return animationView } }スクロール中だけアニメーションを止めてみよう
このままでは
viewDidLoad
ライフサイクルでplay()
されっぱなしで、止めようがありません。
今回の要求として、「スクロール中はアニメーションを止める」というのがありますので、まずはUIScrollView
を Outlet 接続しましょう。// MARK: - IBOutlet // scrollView の子要素で、 Lottie オブジェクトが配置される UIView @IBOutlet weak var contentView: UIView! @IBOutlet weak var backgroundScrollView: UIScrollView! // 追加次に、追加した
backgroundScrollView
の delegate 先をself
つまりViewController
自身に設定します。// MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. backgroundScrollView.delegate = self // 追加これで ScrollView の delegate メソッドが使えるようになりました。
UIScrollView
の delegate メソッドは沢山ありますが、今回は
- スクロールのし初め
- スクロールの終わり
を検知することで
- スクロールのし初め: アニメーションを一時停止
- スクロールの終わり: アニメーションを再開
という風にして実装していきます。
沢山ある delegate メソッドの解説は 公式ドキュメント や 他の方がまとめてくださった記事 に譲って割愛します。
今回使うのは、以下の 2 つです。// 1. // 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド func scrollViewWillBeginDragging(_ scrollView: UIScrollView) // 2. // 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド func scrollViewDidEndDecelerating(_ scrollView: UIScrollView)これらのメソッドを使えば、以下のようにして、簡単にスクロール中にアニメーションを止めて、スクロールが終わったらアニメーションを再開するということが可能になります。
ちなみに、stop()
メソッドを使用していますが、pause()
メソッドでも大丈夫です。
アニメーションが最後まで行って止まるか、pause()
された瞬間に止まるかの違いです。// 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール開始と同時にアニメーションをストップ animations.forEach { $0.stop() } } // 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール終了と同時にアニメーションをスタート animations.forEach { $0.play() } }あとは、これを実装して終わりです。
ViewController にUIScrollViewDelegate
を継承させます。
僕は extension して書いていますが、class ViewController: UIViewController, UIScrollViewDelegate
としても何も問題ありません。// MARK: - UIScrollViewDelegate extension ViewController: UIScrollViewDelegate { // 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール開始と同時にアニメーションをストップ animations.forEach { $0.stop() } } // 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール終了と同時にアニメーションをスタート animations.forEach { $0.play() } } }これでビルドしてみると、スクロールしている間だけアニメーションが止まることを確認できるはずです。
最後に、完成した ViewController を載せます。import UIKit import Lottie class ViewController: UIViewController { // MARK: - IBOutlet // scrollView の子要素で、 Lottie オブジェクトが配置される UIView @IBOutlet weak var contentView: UIView! @IBOutlet weak var backgroundScrollView: UIScrollView! // MARK: - Private properties var animations = [AnimationView]() // MARK: - Lifecycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. backgroundScrollView.delegate = self // ランダムな位置に 3 個の Lottie オブジェクトを生成・配置・再生する // 複数配置していることに特に意味はありません。 for _ in 1...3 { let x = CGFloat.random(in: 1.0...5.0) * 200.0 let y = CGFloat.random(in: 1.0...6.0) * 100.0 let animation = generateAnimation(x: x, y: y) animations.append(animation) contentView.addSubview(animation) animation.play() } } // MARK: - Private methods // 引数で受け取った座標を持つ Lottie オブジェクトを生成するメソッド // see: https://qiita.com/ngo275/items/c9e94bad7a7afc85e4f4 private func generateAnimation(x: CGFloat, y: CGFloat) -> AnimationView { let animationView = AnimationView(name: "sample_animation") animationView.frame = CGRect(x: x, y: y, width: view.bounds.width * 0.2, height: view.bounds.height * 0.2) animationView.loopMode = .loop animationView.contentMode = .scaleAspectFit animationView.animationSpeed = 1 return animationView } } // MARK: - UIScrollViewDelegate extension ViewController: UIScrollViewDelegate { // 指が画面に触れ、スクロールが開始した瞬間に呼ばれるメソッド func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール開始と同時にアニメーションをストップ animations.forEach { $0.stop() } } // 指が画面から離れ、慣性のスクロールが完全に止まった瞬間に呼ばれるメソッド func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { print(#function) // どの関数が呼ばれているか確認用に表示 // スクロール終了と同時にアニメーションをスタート animations.forEach { $0.play() } } }参考文献
- 投稿日:2020-09-18T07:06:50+09:00
Flutter プラグインの中で CoreData のモデル (.xcdatamodeld) を読み込む
実際のところ Flutter プラグイン固有の Tips ではなく、 Pods や Framework の中から .xcdatamodeld をロードする方法の Tips ですが、iOSネイティブ開発慣れしていないと、とても分かりにくいと思ったのでメモとして残しておきます。
問題
Core Data の使い方ガイドでは、以下のようなコードでサクッと MyModel.xcdatamodeld を読み込む方法が紹介されていますが、Flutter プラグインの中に .xcdatamodeld ファイルをもたせた場合、このコードを書いても読み込めません(ちなみに、この段階ではエラーにならず、エンティティ生成時にクラッシュします)。
Objective-C_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MyModel"];原因
問題は2点です。
- Flutter プラグインは、Flutter アプリ本体とは別のフレームワークとしてビルドされますが、Flutter プラグインの初期設定では、.xcdatamodeldがバンドルされません。
- フレームワークにうまく MyModel.xcdatamodeld をバンドルした場合でも、モデル名だけではリソースを読み込めません。フレームワーク内のリソースには、そのリソースを指すパスでアクセスする必要があります。
対策
まず、.xcdatamodeld がフレームワークにバンドルされるようにします。
次のスクリーンショットは plugin_example という名前の Flutter プラグインのフォルダ構造です。
ios/Resources フォルダを作り、そこに MyModel.xcdatamodeld ファイルを置きます。そして、podspec に次の行を追加します。
.podspecs.resources = 'Resources/MyModel.xcdatamodeld'次に、モデル読み込みのコードを以下のとおり書き換えます。
Objective-CNSURL *modelURL = [[NSBundle bundleForClass:self.class] URLForResource:@"MyModel" withExtension:@"momd"]; NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; _persistentContainer = [[NSPersistentContainer alloc] initWithName:@"MyModel" managedObjectModel:model];self.class の self は Flutter プラグインに所属しているクラスのインスタンスを与えます。Flutterプラグインの初期化コードあたりに書けば良いと思います。
拡張子を momd に指定しているのは、MyModel.xcdatamodeld ファイルはビルドされ、 実際には MyModel.xcdatamodel.momd という名称でバンドルに保存されるためです。
- 投稿日:2020-09-18T02:15:11+09:00
Firebase Crashlyticsを導入してCould not get GOOGLE_APP_ID in Google Services file from build environmentが出た場合の対処法
エラー発生
iOSのプロジェクトにFirebase Crashlyticsを導入したところ、以下のエラーが発生。
Could not get GOOGLE_APP_ID in Google Services file from build environmentGoogleService-Info.plistは正しいパスに置いてあるはず。何故…
原因
公式ドキュメントに書いてありました。
注: 新しいビルドフェーズがプロジェクトの最後のビルドフェーズであることを確認してください。そうでないと、Crashlytics が正しく初期化されません。
対象のプロジェクトではXcodgenでプロジェクトファイルを生成していました。
Xcodegenは必ず最後にCocoaPodsのスクリプトが走るようになっているため、正しく初期化されなかったようです。
(postCompileScripts
よりも更に後に走る)解決策
CocoaPodsのscript_phaseで、最後のビルドフェーズにスクリプトが走るようにして解決。
Podfiletarget 'Project' do pod 'Firebase/Crashlytics' script_phase :name=> 'FirebaseCrashlytics', :script=> '"${PODS_ROOT}/FirebaseCrashlytics/run"', :input_files=> ['$(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)'] endビルドフェーズの最後に追加されたことが確認できます。
Build Phases 参考文献
https://qiita.com/yosshi4486/items/73a627c1e3daf8f4f509
Fabricだった頃からあるようです。Xcodegenにスクリプトまとめたいですね…
- 投稿日:2020-09-18T02:09:26+09:00
Firebase Cloud Functions を TypeScriptで実装するための環境準備
A. 概要
これからFirebase Cloud Functions を TypeScriptで実装しようとしている初心者向けに、その環境の準備手順を纏めます。
B. 目次
- Firebaseの準備
- TypeScriptを使用する準備
C. 詳細手順
1. Firebaseの準備
Reference: Firebase 公式ドキュメント
1) Firebaseプロジェクトを作成する ・・・ 委細省略
2) Node.js と Firebase CLI を設定する ・・・ クライアントPC毎にワンタイム
a. Node.jsをインストール(公式サイト) *JavaScript実行環境
b. npmをインストール *Node.jsのパッケージ管理システム
c. Firebase CLI をインストール// npmを使用してインストールする場合 npm install -g firebase-toolsd. Firebase CLI と SDK の両方を更新
// Firebase プロジェクトの functions フォルダ内で次のコマンドを使用 npm install firebase-functions@latest firebase-admin@latest --save npm install -g firebase-tools3) Firebase SDK for Cloud Functions を初期化する
a. firebase login を実行してブラウザからログインし、firebase ツールを認証
b. Firebase プロジェクトのディレクトリに移動
c. firebase init functions を実行。以下、実行例(既作成のプロジェクトがある場合)(ターミナルの実行結果例)hogeMBP:$ firebase login (略) hogeMBP:$ firebase init functions ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You're about to initialize a Firebase project in this directory: /Users/fuga/Xcode/foo === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project. ? Please select an option: Don't set up a default project // このオプションを選択 === Functions Setup A functions directory will be created in your project with a Node.js package pre-configured. Functions can be deployed with firebase deploy. ? What language would you like to use to write Cloud Functions? TypeScript // TypeScriptを選択 ? Do you want to use TSLint to catch probable bugs and enforce style? Yes // YESを選択 ✔ Wrote functions/package.json ✔ Wrote functions/tslint.json ✔ Wrote functions/tsconfig.json ✔ Wrote functions/src/index.ts ✔ Wrote functions/.gitignore ? Do you want to install dependencies with npm now? Yes // YESを選択 > protobufjs@6.10.1 postinstall /Users/fuga/Xcode/foo/functions/node_modules/protobufjs > node scripts/postinstall npm notice created a lockfile as package-lock.json. You should commit this file. added 285 packages from 217 contributors and audited 285 packages in 9.616s found 0 vulnerabilities i Writing configuration info to firebase.json... i Writing project information to .firebaserc... i Writing gitignore file to .gitignore... ✔ Firebase initialization complete!なお、上記で選択した以外のオプションを選択すると、下記のようなエラーが発生した。。
**これは、Authエラーがあったため。本来なら、「Use an existing project」を選択すべきでした。上記のようにプロジェクトを追加しないで先に進めると、デプロイの前に、「firebase use --add」を実行する必要があります。
関連:Cloud Functions for Firebase で deploy 先を切り替える(エラーの例1)? Please select an option: Use an existing project // 「Use an existing project」を選択 Error: Failed to list Firebase projects. See firebase-debug.log for more info.(エラーの例2)? Please select an option: Create a new project // 「Create a new project」を選択 i If you want to create a project in a Google Cloud organization or folder, please use "firebase projects:create" instead, and return to this command when you've created the project. ? Please specify a unique project id (warning: cannot be modified afterward) [6-30 characters]: hogehoge // FirebaseのプロジェクトIDを入力 ? What would you like to call your project? (defaults to your project ID) hogehoge // FirebaseのプロジェクトIDを入力 ✖ Creating Google Cloud Platform project Error: Failed to create project. See firebase-debug.log for more info.この処理で、クライアントPCのプロジェクトフォルダ内に、以下の構成が生成されます。
TypeScriptを利用する上で必要となる「package.json」や「tsconfig.json」はここで自動生成されます。(ファイル構成)myproject +- functions/ # Directory containing all your functions code | +- package.json # npm package file describing your Cloud Functions code | +- tsconfig.json | +- tslint.json # Optional file | +- src/ # Directory containing TypeScript source | | | +- index.ts # main source file for your Cloud Functions code | +- lib/ | +- index.js # Built/transpiled JavaScript code | +- index.js.map # Source map for debugging2. TypeScriptを使用する準備
TypeScriptコンパイラをインストール
Reference: TypeScriptチュートリアル① -環境構築編-
a. (Node.jsをインストール) *上記で既にインストール済み
b. TypeScriptコンパイラをインストール// npmを使用してインストールする場合 npm install -g typescriptc. インストールの確認 ・・・ ターミナルで「tsc -v」を実行し、バージョンが表示されたらOK
IDEを利用する準備
Visual Studio Code(VS Code)をインストール・初期設定
公式サイト
Reference: MacOSでVisual Studio Codeをインストールする手順VS Codeのビルド設定を作成 *プロジェクトファイル毎に実施
Reference:
A. Visual Studio Code で TypeScript 環境を設定してデバッグ実行する
B. VSCode で TypeScript のデバック設定a. プロジェクトフォルダを開く。上記のフォルダ構成の場合、「functions」の位置(Not「src」)
b. ビルドタスクの設定(tasks.json)Visual Studio Code のメニュー 「ターミナル」 > 「既定のビルドタスクの構成」を選択 > 「tsc: ビルド - tsconfig.json」 を選択または
shift + command + p > 「Tasks: Configure Default Build Task」を開く > 「tsc: ビルド - tsconfig.json」 を選択作成した「tasks.json」は以下のような内容。
tasks.json{ "version": "2.0.0", "tasks": [ { "type": "typescript", "tsconfig": "tsconfig.json", "problemMatcher": [ "$tsc" ], "group": { "kind": "build", "isDefault": true }, "label": "tsc: ビルド - tsconfig.json" } ] }c. 起動設定(launch.json)
「実行」 > 「構成の追加」を選択 > 「Node.js」を選択作成した「launch.json」は以下のような内容。
ここで、「preLaunchTask」の値は、「tasks.json」の"label"の値に変更。launch.json{ "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "プログラムの起動", "skipFiles": [ "<node_internals>/**" ], "program": "${workspaceFolder}/lib/index.js", "preLaunchTask": "tsc: ビルド - tsconfig.json" } ] }ここまでの設定で、VS Codeでデバッグ、コンパイルを実行できます(「実行」>「デバッグの開始」)。