- 投稿日:2020-02-22T23:35:30+09:00
「.swiftlint.yml」のルールが機能しなくハマったこと
.swiftlint.ymlに
line_length
のルールを記載したのに反映されないことがありハマりました。
AppDelegateなどでコメントのwarningがでていたのでLintに以下のルールを追加しましたが、反映されませんでした。.swiftlint.ymlline_length: warning: 300 ignores_comments: trueSwiftLintのバージョンが古いとか、RULEに半角スペースが3つあるとかでも反映されないような現象があるみたいですが、
私の場合は違いました。試したこと
- Homebrewをupgradeしてswiftlintの入れ直し。
- YAMLが正しく書いているか確認。
- YAMLlint - The YAML Validator http://www.yamllint.com/
- swiftlintのupgrade / swiftlintのuninstall
結論
.swiftlint.yml
はxxx.xcodeproj
と同じ階層にないと反映されない。
私の場合、githubからcloneしてきたので以下のディレクトリの構成になっていました。beforeSampleProject ├── SampleProject │ ├── SampleProject/... │ ├── SampleProject.xcodeproj │ └── SampleProjectTests ├── .swiftlint.yml ├── README.md ├── .gitignore └── .git以下のようにルートディレクトリに
xxx.xcode.proj
と.swiftlint.yml
を同じ階層に置けばルールが適応されました。afterSampleProject ├── SampleProject/... ├── SampleProject.xcodeproj ├── SampleProjectTests ├── .swiftlint.yml ├── README.md ├── .gitignore └── .gitcloneしてからプロジェクトファイルを作るとプロジェクト名のディレクトリがダブるので、
プロジェクトファイルを作成してから、git clone http://xxxxxx.com/xxxxxx.git .
をした方がよさそうと思った。
最後の.
はリポジトリと同名のディレクトリを作らないためにつける。参考
How to use swiftlint command and Xcode Script when .swiftlint.yml file not in the same folder with project.xcodeproj? · Issue #2673 · realm/SwiftLint
https://github.com/realm/SwiftLint/issues/2673git clone でディレクトリを作らない | 株式会社Orfool
https://orfool.com/programing/2206/SwiftLintを試してみた - Qiita
https://qiita.com/ushisantoasobu/items/b494c9cf7d78a968b373SwiftLintのdisabled_rules一覧(実例付き) - Qiita
https://qiita.com/akatsuki174/items/6da27f7ccdd0572f8927SwiftLintの運用ノウハウ - Qiita
https://qiita.com/shtnkgm/items/6dd756aa14926736c6f5
- 投稿日:2020-02-22T18:52:47+09:00
[Apple] 写経 Start Developing iOS Apps (Swift)
Apple公式ドキュメント Start Developing iOS Apps (Swift) の一部を写経します。
appデリゲートソースファイル
AppDelegate.swift
には、2つの主要な関数があります。
AppDelegate
クラスを定義します。appデリゲートは、コンテンツが描画されるウィンドウを作成し、アプリケーション内の状態遷移を受け付けるための場所を提供します。- アプリケーションのエントリポイントと、入力イベントをアプリケーションへ渡す実行ループを作成します。これはファイル先頭にある
UIApplicationMain
属性(@UIApplicationMain
)によって行われます。UIApplicationMain
属性は、デリゲートクラスとしてAppDelegate
クラスをUIApplicationMain
へ渡すことと同義です。戻り値の中で、システムはアプリケーションオブジェクトを作成します。アプリケーションオブジェクトはアプリケーションのライフサイクルを管理する責任を負います。システムはAppDelegate
クラスのインスタンスもまた作成しアプリケーションオブジェクトへ割り当てます。最終的にシステムはアプリケーションを起動します。わかったこと:
アプリケーションの実行時に、アプリケーションオブジェクトとappデリゲートが作成される。
UIApplicationMain
属性をつけることで、アプリケーションのエントリポイントとappデリゲートであることをシステムに認識させる。
AppDelegate
クラスはプロパティを一つ含みます。var window: UIWindow?このプロパティはアプリケーションのウィンドウへの参照を格納しています。ウィンドウはアプリケーションのビュー階層の根元です。コンテンツはこの中に描画されます。このwindowプロパティはオプショナルであることに注意が必要です。ある時点では値が入っていません(nilです)。
AppDelegate
クラスは以下のメソッドのスタブ実装も含んでいます。func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool func applicationWillResignActive(_ application: UIApplication) func applicationDidEnterBackground(_ application: UIApplication) func applicationWillEnterForeground(_ application: UIApplication) func applicationDidBecomeActive(_ application: UIApplication) func applicationWillTerminate(_ application: UIApplication)これらのメソッドは、アプリケーションオブジェクトにappデリゲートとやりとりさせるためのものです。
いずれのデリゲートメソッドも既定の振る舞いを持っています。メソッドが空か存在しないと、既定の振る舞いをします。
わかったこと:
AppDelegate
プロトコルを実装することでアプリケーションオブジェクトとやりとりする。
AppDelegate
プロトコルの中身を実装しないと、既定の振る舞いをする。
- 投稿日:2020-02-22T18:12:29+09:00
Xcodeライブラリの追加方法
はじめに
この記事ではxcodeにpodでライブラリを追加した時の手順を記していきます。
また、ターミナルやvimの操作は前提に進めさせていただきますmm準備
- xcodeを起動する
- プロジェクトを作成する
Podをインストールする
ターミナルを立ち上げてcocoapodsを設定していく。
// cocoapodsをインストールする sudo gem install cocoapods cd {プロジェクト} pod setup // Podfileを生成する pod initcocoapodsでライブラリのバージョン管理をしていくので、
ここまででインストールの準備完了!Podfileの編集
# エディタでPodfileを開き、下記のように編集する vim Podfile# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target '{プロジェクト名}' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for {プロジェクト名} pod 'RealmSwift' // ←ここに入れたいライブラリを追加する!! target '{プロジェクト名}Tests' do inherit! :search_paths # Pods for testing end target '{プロジェクト名}UITests' do # Pods for testing end endライブラリのインストール
// 初回はこちらで実行 pod install // 2回目以降はupdateを使う pod updateこれでプロジェクトにPodsというディレクトリができていればOK!
Xcodeを起動
Xcode初心者の私としてはここが意外とハマりどころだと思う!
open -a "/Applications/Xcode.app" {プロジェクト名}.xcworkspaceライブラリのimport
これでライブラリがimportできるはず!
と思いきやRealm(今回インストールしたライブラリ)が表示されない。。左のディレクトリから自分で作成したプロジェクトを選択
↓
上の方のバーからBuild Phasesを選択
↓
一覧からLink Binary With Librariesを選択
↓
+ボタンからインストールしたライブラリを追加
↓
cmd + Rで一旦ビルド調査したらこれらのxcodeに追加する手順が必要そうでした!
まとめ
ライブラリをimportしようとしたときにcocoapodsの設定までとimport後の記事が多くて手間取ったので、
cocoapodsのインストールからライブラリをimportするところまでまとめてみました!
間違いや編集ミスあればコメントくださいmm
少しでもxcodeで環境構築する時のお役に立てればと思いますー!
- 投稿日:2020-02-22T16:31:28+09:00
Amazon TranscribeとSwiftから連携してみた
Amazon Transcribeとは
音声ファイルから自動的に文字を起こしてくれるAmazonのサービスです。
https://aws.amazon.com/jp/transcribe/
類似のものに、Google CloudのSpeech-to-Textがあります。
https://cloud.google.com/speech-to-text?hl=ja今回説明しないもの
・AWSの設定の詳細
・S3へのアップロード、ダウンロードの詳細
・動画や音声の詳細準備
AWS
・Cognito
iOSアプリからAWSにアクセスするためのやつ。
・S3
Transcribeに使う音声ファイルやjobファイルを置くストレージ。
・Transcribe
本丸。Create Jobから特に困ることなく設定可能です。iOS
・AWS SDK for iOS
AWSS3とAWSTranscribeだけあれば大丈夫です。(AWSCoreもついてくるので。)おおまかな流れ
iOSから音声(動画)ファイルをS3にアップロード
-> iOSからTranscribeのjobを実行命令
-> AWSのTranscribeがjobを実行し、先ほどアップロードしたS3のファイルを文字化
-> Transcribeの結果取得
-> 変換結果のjsonファイルをS3からダウンロード実装
初期設定
SceneDelegate.swiftfunc scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // AWS Cognito & S3 & Transcribe registration let credentialProvider = AWSCognitoCredentialsProvider(regionType: .APNortheast1, identityPoolId: "ap-northeast-1:*******************") if let configuration = AWSServiceConfiguration(region:.APNortheast1, credentialsProvider:credentialProvider) { AWSS3TransferUtility.register(with:configuration!, forKey: "MY_S3") AWSTranscribe.register(with:configuration!, forKey: "MY_Transcribe") } }SceneDelegate(AppDelegate)のおなじみの起動ファンクションでAWS初期設定します。
Cognitoでregionと設定の際に発行されたIdentity Pool IDを設定します。
設定後、Transcribe、 AWSS3TransferUtilityへの登録も行っておきます。S3へ該当ファイルをアップロード
let bucketName = "mybucket" let mp4URL = URL(fileURLWithPath: "your/video.mp4") if let awss3 = AWSS3TransferUtility.s3TransferUtility(forKey: "MY_S3") { do { let videoData = try Data(contentsOf: mp4URL) let videoName = "s3video.mp4" awss3.uploadData( videoData, bucket: bucketName, key: videoName, contentType: "mp4", expression: nil, // 途中経過task nullable completionHandler: { task, error in if let error = error { print("s3 upload error \(error)") } else { print("success upload") } } catch let error { print("data convert error \(error)") } }今回はmp4ファイルで行います。Transcribeがフォローしているファイルタイプは、flac, mp3, mp4, wavの4タイプです。(2020/2/20現在)
録画なりして端末に入ってるmp4ファイルをData型で取得し、AWSS3TransferUtility.uploadDataでアップロードします。
ここでのbucketはCognitoで設定したものと同じでなくてはなりません。
expressionはアップロードの途中経過を取得できるtaskですが今回は省きます。
また、uploadData自体もTaskとして登録できるものもあるので使い分けてください。TranscribeのJobを実行
let awstrans = AWSTranscribe(forKey: "MY_Transcribe") let jobName = "MY_JOB" if let startRequest = AWSTranscribeStartTranscriptionJobRequest() { startRequest.languageCode = .jaJP // language code結構いっぱいある let media = AWSTranscribeMedia() media?.mediaFileUri = "https://s3-ap-northeast-1.amazonaws.com/\(bucketName)/\(videoName)" startRequest.media = media // 先ほどアップロードしたs3のファイルのurlを指定する startRequest.mediaFormat = .mp4 // flac, mp3, mp4, wav startRequest.mediaSampleRateHertz = 44100 // いらないかも startRequest.transcriptionJobName = jobName startRequest.outputBucketName = bucketName // Job実行 awstrans.startTranscriptionJob(startRequest, completionHandler: {response, error in if let error = error { print("start job error \(error)") } else { print("success start job") } } }AWSTranscribeStartTranscriptionJobRequestでJobのステータスを設定します。
ここで勘違いしていたのが、startTranscriptionJobのcompletionがJob完了時に呼ばれるものだと思ってましたが、これはあくまでJobがスタートした時に呼ばれるものでした。TranscribeのJobが完了するまで待って取得
// timer使うので DispatchQueue.main.async { self.timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: { timer in if let getJobRequest = AWSTranscribeGetTranscriptionJobRequest() { getJobRequest.transcriptionJobName = projectId awstrans.getTranscriptionJob(getJobRequest, completionHandler: {response, error in if let error = error { print("get job error \(error)") self.timer.invalidate() } if let reason = response?.transcriptionJob?.failureReason { print("job failed \(reason)") } if response?.transcriptionJob?.transcriptionJobStatus == .completed { // 完了後、awsにアップロードされた、結果の記載されたjsonのuriが取得できる print(response?.transcriptionJob?.transcript?.transcriptFileUri) self.timer.invalidate() } }) } }) }探してみたところ、Jobの完了通知をしてくれるものは見当たらなかったので、取り急ぎTimerで完了するまでgetし続けるという原始的なことをしました。しかも、mp4ファイルだと3MBぐらいのサイズでも40秒とかかかったので、interval=10としました。
JobStatus=completeとなった段階で、transcriptionJobにいろいろな値がセットされて返却されるので、結果の記載されたjsonのURIを取得する。
余談ですが、ハンドラ内でTimerを実行する際は実行スレッドに注意。S3からTranscribeの結果jsonを取得
awss3.downloadData( fromBucket: bucketName, key: projectId + ".json", expression: nil, completionHandler:{task, location, data, error in if let error = error { print("s3 download error \(error)") } else { if let data = data { do { let jsonDecoder = JSONDecoder() let transcribeData = try jsonDecoder.decode(AmazonTranscribe.self, from: data) print(transcribeData.results.transcripts) } catch let error { print("decode error \(error)") } } } } )AmazonTranscribe.swiftstruct AmazonTranscribe: Decodable { let jobName:String let accountId:String let results: AmazonTranscribeResults let status: String } struct AmazonTranscribeResults: Decodable { let transcripts: [AmazonTranscribeTranscripts] let items: [AmazonTranscribeItem] } struct AmazonTranscribeTranscripts: Decodable { let transcript: String // 全文 } struct AmazonTranscribeItem: Decodable { let startTime: Double let endTime: Double let alternatives: [AmazonTranscribeAlternatives] let type: String private enum CodingKeys: String, CodingKey { case startTime = "start_time" case endTime = "end_time" case alternatives, type } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) guard let startTimeDouble = Double(try values.decode(String.self, forKey: .startTime)) else { fatalError("The start time is not an Double") } guard let endTimeDouble = Double(try values.decode(String.self, forKey: .endTime)) else { fatalError("The end time is not an Double") } startTime = startTimeDouble endTime = endTimeDouble alternatives = try values.decode([AmazonTranscribeAlternatives].self, forKey: .alternatives) type = try values.decode(String.self, forKey: .type) } } struct AmazonTranscribeAlternatives: Decodable { let confidence: Double let content: String private enum CodingKeys: String, CodingKey { case confidence, content } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) guard let confidenceDouble = Double(try values.decode(String.self, forKey: .confidence)) else { fatalError("The confidence is not an Double") } confidence = confidenceDouble content = try values.decode(String.self, forKey: .content) } }おまけでjsonをDecodeするのにつかったDecodableも載せておきます。返却値はこんな(AmazonTranscribe.swift)感じです。
各値の意味は深く調べてませんが、全文(なぜリスト?)と代替候補っぽいのはありました。感想
正直なところ、日本語の文字起こし精度はさほど高くないように感じられました。
ファイルの形式等を調整したらもうちょい精度あがりそうですが、
試しにGoogleHomeとの、「ねぇGoogle、明日の天気は?」「明日の新宿は最高気温15度、最低気温7度で晴れるでしょう」というやりとり動画を送信してみたら、
「めぐる 明日 の 天気 は 明日 の 新宿 は 最高 気温 十 五 度 再 激 音 など で 買える でしょ」
というアウトプットがきました。この場合の使える情報は、明日の新宿が最高気温15度ってことだけでしょう。
英語はしゃべれないし、発音も悪いので試してません。
- 投稿日:2020-02-22T16:31:28+09:00
Amazon TranscribeにSwiftアプリから連携してみた
Amazon Transcribeとは
音声ファイルから自動的に文字を起こしてくれるAmazonのサービスです。
https://aws.amazon.com/jp/transcribe/
類似のものに、Google CloudのSpeech-to-Textがあります。
https://cloud.google.com/speech-to-text?hl=ja今回説明しないもの
・AWSの設定の詳細
・S3へのアップロード、ダウンロードの詳細
・動画や音声の詳細準備
AWS
・Cognito
iOSアプリからAWSにアクセスするためのやつ。
・S3
Transcribeに使う音声ファイルやjobファイルを置くストレージ。
・Transcribe
本丸。Create Jobから特に困ることなく設定可能です。iOS
・AWS SDK for iOS
AWSS3とAWSTranscribeだけあれば大丈夫です。(AWSCoreもついてくるので。)おおまかな流れ
iOSから音声(動画)ファイルをS3にアップロード
-> iOSからTranscribeのjobを実行命令
-> AWSのTranscribeがjobを実行し、先ほどアップロードしたS3のファイルを文字化
-> Transcribeの結果取得
-> 変換結果のjsonファイルをS3からダウンロード実装
初期設定
SceneDelegate.swiftfunc scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // AWS Cognito & S3 & Transcribe registration let credentialProvider = AWSCognitoCredentialsProvider(regionType: .APNortheast1, identityPoolId: "ap-northeast-1:*******************") if let configuration = AWSServiceConfiguration(region:.APNortheast1, credentialsProvider:credentialProvider) { AWSS3TransferUtility.register(with:configuration!, forKey: "MY_S3") AWSTranscribe.register(with:configuration!, forKey: "MY_Transcribe") } }SceneDelegate(AppDelegate)のおなじみの起動ファンクションでAWS初期設定します。
Cognitoでregionと設定の際に発行されたIdentity Pool IDを設定します。
設定後、Transcribe、 AWSS3TransferUtilityへの登録も行っておきます。S3へ該当ファイルをアップロード
let bucketName = "mybucket" let mp4URL = URL(fileURLWithPath: "your/video.mp4") if let awss3 = AWSS3TransferUtility.s3TransferUtility(forKey: "MY_S3") { do { let videoData = try Data(contentsOf: mp4URL) let videoName = "s3video.mp4" awss3.uploadData( videoData, bucket: bucketName, key: videoName, contentType: "mp4", expression: nil, // 途中経過task nullable completionHandler: { task, error in if let error = error { print("s3 upload error \(error)") } else { print("success upload") } } catch let error { print("data convert error \(error)") } }今回はmp4ファイルで行います。Transcribeがフォローしているファイルタイプは、flac, mp3, mp4, wavの4タイプです。(2020/2/20現在)
録画なりして端末に入ってるmp4ファイルをData型で取得し、AWSS3TransferUtility.uploadDataでアップロードします。
ここでのbucketはCognitoで設定したものと同じでなくてはなりません。
expressionはアップロードの途中経過を取得できるtaskですが今回は省きます。
また、uploadData自体もTaskとして登録できるものもあるので使い分けてください。TranscribeのJobを実行
let awstrans = AWSTranscribe(forKey: "MY_Transcribe") let jobName = "MY_JOB" if let startRequest = AWSTranscribeStartTranscriptionJobRequest() { startRequest.languageCode = .jaJP // language code結構いっぱいある let media = AWSTranscribeMedia() media?.mediaFileUri = "https://s3-ap-northeast-1.amazonaws.com/\(bucketName)/\(videoName)" startRequest.media = media // 先ほどアップロードしたs3のファイルのurlを指定する startRequest.mediaFormat = .mp4 // flac, mp3, mp4, wav startRequest.mediaSampleRateHertz = 44100 // いらないかも startRequest.transcriptionJobName = jobName startRequest.outputBucketName = bucketName // Job実行 awstrans.startTranscriptionJob(startRequest, completionHandler: {response, error in if let error = error { print("start job error \(error)") } else { print("success start job") } } }AWSTranscribeStartTranscriptionJobRequestでJobのステータスを設定します。
ここで勘違いしていたのが、startTranscriptionJobのcompletionがJob完了時に呼ばれるものだと思ってましたが、これはあくまでJobがスタートした時に呼ばれるものでした。TranscribeのJobが完了するまで待って取得
// timer使うので DispatchQueue.main.async { self.timer = Timer.scheduledTimer(withTimeInterval: 10, repeats: true, block: { timer in if let getJobRequest = AWSTranscribeGetTranscriptionJobRequest() { getJobRequest.transcriptionJobName = projectId awstrans.getTranscriptionJob(getJobRequest, completionHandler: {response, error in if let error = error { print("get job error \(error)") self.timer.invalidate() } if let reason = response?.transcriptionJob?.failureReason { print("job failed \(reason)") } if response?.transcriptionJob?.transcriptionJobStatus == .completed { // 完了後、awsにアップロードされた、結果の記載されたjsonのuriが取得できる print(response?.transcriptionJob?.transcript?.transcriptFileUri) self.timer.invalidate() } }) } }) }探してみたところ、Jobの完了通知をしてくれるものは見当たらなかったので、取り急ぎTimerで完了するまでgetし続けるという原始的なことをしました。しかも、mp4ファイルだと3MBぐらいのサイズでも40秒とかかかったので、interval=10としました。
JobStatus=completeとなった段階で、transcriptionJobにいろいろな値がセットされて返却されるので、結果の記載されたjsonのURIを取得する。
余談ですが、ハンドラ内でTimerを実行する際は実行スレッドに注意。S3からTranscribeの結果jsonを取得
awss3.downloadData( fromBucket: bucketName, key: projectId + ".json", expression: nil, completionHandler:{task, location, data, error in if let error = error { print("s3 download error \(error)") } else { if let data = data { do { let jsonDecoder = JSONDecoder() let transcribeData = try jsonDecoder.decode(AmazonTranscribe.self, from: data) print(transcribeData.results.transcripts) } catch let error { print("decode error \(error)") } } } } )AmazonTranscribe.swiftstruct AmazonTranscribe: Decodable { let jobName:String let accountId:String let results: AmazonTranscribeResults let status: String } struct AmazonTranscribeResults: Decodable { let transcripts: [AmazonTranscribeTranscripts] let items: [AmazonTranscribeItem] } struct AmazonTranscribeTranscripts: Decodable { let transcript: String // 全文 } struct AmazonTranscribeItem: Decodable { let startTime: Double let endTime: Double let alternatives: [AmazonTranscribeAlternatives] let type: String private enum CodingKeys: String, CodingKey { case startTime = "start_time" case endTime = "end_time" case alternatives, type } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) guard let startTimeDouble = Double(try values.decode(String.self, forKey: .startTime)) else { fatalError("The start time is not an Double") } guard let endTimeDouble = Double(try values.decode(String.self, forKey: .endTime)) else { fatalError("The end time is not an Double") } startTime = startTimeDouble endTime = endTimeDouble alternatives = try values.decode([AmazonTranscribeAlternatives].self, forKey: .alternatives) type = try values.decode(String.self, forKey: .type) } } struct AmazonTranscribeAlternatives: Decodable { let confidence: Double let content: String private enum CodingKeys: String, CodingKey { case confidence, content } init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) guard let confidenceDouble = Double(try values.decode(String.self, forKey: .confidence)) else { fatalError("The confidence is not an Double") } confidence = confidenceDouble content = try values.decode(String.self, forKey: .content) } }おまけでjsonをDecodeするのにつかったDecodableも載せておきます。返却値はこんな(AmazonTranscribe.swift)感じです。
各値の意味は深く調べてませんが、全文(なぜリスト?)と代替候補っぽいのはありました。感想
正直なところ、日本語の文字起こし精度はさほど高くないように感じられました。
ファイルの形式等を調整したらもうちょい精度あがりそうですが、
試しにGoogleHomeとの、「ねぇGoogle、明日の天気は?」「明日の新宿は最高気温15度、最低気温7度で晴れるでしょう」というやりとり動画を送信してみたら、
「めぐる 明日 の 天気 は 明日 の 新宿 は 最高 気温 十 五 度 再 激 音 など で 買える でしょ」
というアウトプットがきました。この場合の使える情報は、明日の新宿が最高気温15度ってことだけでしょう。
英語はしゃべれないし、発音も悪いので試してません。
- 投稿日:2020-02-22T16:09:13+09:00
Swift UI + SwinjectでDI(Dependency Injection)する方法
SwiftUIでSwinjectなどのDIコンテナを使ってDIするときの解決策として、ネストした型を使う方法を考えてみたので共有します。
SwiftUIの場合、どこでDIする?
SwinjectでDIするとき、従来のStoryboardならSwinjectStoryboardというものが用意されていました。SwiftUIの場合はどうしたらよいでしょうか?
例えば、次のコードの
SomeContentView
をDIしたいときはどうすればよいか?'SwiftUIの例struct BookMarkRow: View { var body: some View { HStack { Text(bookMark.title) NavigationLink(destination: SomeContentView()) { Text("Next Page") } } } }解決策
まず、
DependencyInjectable
というDI可能なprotocolを宣言します。DIできるprotocolの宣言protocol DependencyInjectable { associatedtype DependencyType var di: DependencyType! {get set} func resolveDependencyInstance() -> DependencyType } extension DependencyInjectable { internal func resolveDependencyInstance() -> DependencyType { let container = DIContainer.shared.getContainer() return container.resolve(type(of: self.di))!! } }次に、これを実装したstructを作ります。
struct Dependency
の中に依存するオブジェクトが注入されます。DIできる型を実装したstructstruct SomeContentView: DependencyInjectable { typealias DependencyType = Dependency struct Dependency { var p: TestProtocol } var di: Dependency! init() { di = resolveDependencyInstance() } func use() { di.p.test() } }依存性を解決するDIコンテナを宣言します。
※SomeProtocol
やSomeProtocolImpl
は何でも良いので省略しますDIコンテナの宣言class DIContainer { static let shared = DIContainer() private init () {} func getContainer() -> Container { let container = Container() container.register(SomeProtocol.self) { r in return SomeProtocolImpl() } container.register(SomeContentView.DependencyType.self) { r in return SomeContentView.DependencyType(p: container.resolve(SomeProtocol.self)!) } return container } }実際に使うときは次のようにすれば、
SomeContentView
は依存性が注入された形でインスタンスが生成されます。使い方let c = SomeContentView() c.use()まとめ
DependencyType
の中に依存するオブジェクトを宣言しておけば、あとはDIコンテナ内で依存関係を解決してやれば良いので、手間いらずだと思います。extensionの中にDIコンテナが入るので、Service Locatorのようにも見えますが、依存するものはすべて
DependencyType
の実装の中で確定し、依存するものはすべてDIコンテナの中で生成しているので、Service Locatorのように依存関係がわかりづらくなることはないと思います。ほかに、もっと良い方法があれば教えて下さい。
- 投稿日:2020-02-22T12:09:03+09:00
Swift公式リファレンス色々とちょっと調べるときの@勉強メモ
Swift公式リファレンス
ちょっと公式がどれなのかわかりづらかったので...!
公式はこちら -> the swift programming languageXcodeのコードから調べる
Xcodeの画面上のコードからcommandを押しながらクリックする>メニュー
- Jump to Definition メソッドや定数の定義に移動(command+click)
- Show quick Help ヘルプを表示(option+click)
- Edit All in Scope ファイル内のローカル変数を一括で変更する
- Rename カーソルがあるクラスやメソッド、変数のリネームを行う
初心者でもわかりやすい
- 投稿日:2020-02-22T01:00:14+09:00
【iOS】UICircularProgressRingを試す
UICircularProgressRingとは?
↓ こちらを見れば一目瞭然ですが、よく見かける丸型のProgressを簡単に実現できるライブラリです。
環境構築
UICircularProgressRing
自体はCocoaPods
やCarthage
でも導入できます。
今回はCarthage
で導入します。
まずは以下の内容でCartfile
を作成。github "luispadron/UICircularProgressRing"次に以下コマンドで手元にダウンロード&ビルドします。
$ carthage update --platform iOSXcodeに戻ってTargetsのGeneralの「Framework, Libraries, and Embedded Content」に
./Carthage/Build/iOS/UICircularProgressRing.framework
を追加。
次に「Build Phases」で新規に「New Run Script Phases」を追加し
/usr/local/bin/carthage copy-frameworks上記を設定し、
Input Files
に$(SRCROOT)/Carthage/Build/iOS/UICircularProgressRing.framework
を設定します。
ここまででライブラリの導入は完了になります。実装
簡単なサンプル
README に乗っていた単純なサンプルを少し修正して試してみます。
import UIKit import UICircularProgressRing class ViewController: UIViewController { private let progressRing = UICircularProgressRing() override func viewDidLoad() { super.viewDidLoad() progressRing.maxValue = 50 progressRing.frame = CGRect(x: 0, y: 0, width: 200, height: 200) progressRing.center = self.view.center self.view.addSubview(progressRing) } // Viewのレイアウトが決定した後じゃないとアニメーションされない override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) progressRing.startProgress(to: 50, duration: 2.0) { print("Done animating!") } } }
UICircularRingStyle
UICircularProgressRing
で設定できるスタイルとして以下があります。
- inside (デフォルト ↑と同じ)
中のテキストを非表示にする
以下のプロパティ
shouldShowValueText
をfalse
に設定すれば非表示になります。progressRing.shouldShowValueText = false
リングカラーを設定する
progressRing.outerRingColor = UIColor.red progressRing.innerRingColor = UIColor.blue
開始アングルを設定する
startAngle
で設定可能。デフォルトは0
で設定されています。
UICircularTimerRing
Timerを設定できる様にしたクラスです。
import UIKit import UICircularProgressRing class ViewController: UIViewController { private let timerRing = UICircularTimerRing() override func viewDidLoad() { super.viewDidLoad() timerRing.frame = CGRect(x: 0, y: 0, width: 200, height: 200) timerRing.center = self.view.center self.view.addSubview(timerRing) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) timerRing.startTimer(to: 10) { state in print("state: \(state)") } } }↑のサンプルでは10秒後にリングが閉じる様に設定しています。
参考になったURL