20200222のSwiftに関する記事は8件です。

「.swiftlint.yml」のルールが機能しなくハマったこと

.swiftlint.ymlにline_lengthのルールを記載したのに反映されないことがありハマりました。
AppDelegateなどでコメントのwarningがでていたのでLintに以下のルールを追加しましたが、反映されませんでした。

.swiftlint.yml
line_length:
  warning: 300
  ignores_comments: true

SwiftLintのバージョンが古いとか、RULEに半角スペースが3つあるとかでも反映されないような現象があるみたいですが、
私の場合は違いました。

試したこと

  • Homebrewをupgradeしてswiftlintの入れ直し。
  • YAMLが正しく書いているか確認。
  • swiftlintのupgrade / swiftlintのuninstall

結論

.swiftlint.ymlxxx.xcodeprojと同じ階層にないと反映されない。
私の場合、githubからcloneしてきたので以下のディレクトリの構成になっていました。

before
SampleProject
    ├── SampleProject
    │   ├── SampleProject/...
    │   ├── SampleProject.xcodeproj
    │   └── SampleProjectTests
    ├── .swiftlint.yml
    ├── README.md
    ├── .gitignore
    └── .git

以下のようにルートディレクトリにxxx.xcode.proj.swiftlint.ymlを同じ階層に置けばルールが適応されました。

after
SampleProject
    ├── SampleProject/...
    ├── SampleProject.xcodeproj
    ├── SampleProjectTests
    ├── .swiftlint.yml
    ├── README.md
    ├── .gitignore
    └── .git

cloneしてからプロジェクトファイルを作るとプロジェクト名のディレクトリがダブるので、
プロジェクトファイルを作成してから、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/2673

git clone でディレクトリを作らない | 株式会社Orfool
https://orfool.com/programing/2206/

SwiftLintを試してみた - Qiita
https://qiita.com/ushisantoasobu/items/b494c9cf7d78a968b373

SwiftLintのdisabled_rules一覧(実例付き) - Qiita
https://qiita.com/akatsuki174/items/6da27f7ccdd0572f8927

SwiftLintの運用ノウハウ - Qiita
https://qiita.com/shtnkgm/items/6dd756aa14926736c6f5

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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プロトコルの中身を実装しないと、既定の振る舞いをする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcodeライブラリの追加方法

はじめに

この記事ではxcodeにpodでライブラリを追加した時の手順を記していきます。
また、ターミナルやvimの操作は前提に進めさせていただきますmm

準備

  • xcodeを起動する
  • プロジェクトを作成する

Podをインストールする

ターミナルを立ち上げてcocoapodsを設定していく。

// cocoapodsをインストールする
sudo gem install cocoapods
cd {プロジェクト}
pod setup

// Podfileを生成する
pod init

cocoapodsでライブラリのバージョン管理をしていくので、

ここまででインストールの準備完了!

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できるはず!
スクリーンショット 2020-02-22 17.57.17.png
と思いきやRealm(今回インストールしたライブラリ)が表示されない。。

スクリーンショット 2020-02-22 18.00.06.png

左のディレクトリから自分で作成したプロジェクトを選択

上の方のバーからBuild Phasesを選択

一覧からLink Binary With Librariesを選択

+ボタンからインストールしたライブラリを追加

cmd + Rで一旦ビルド

調査したらこれらのxcodeに追加する手順が必要そうでした!

スクリーンショット 2020-02-22 18.06.49.png
Realm系のライブラリがimportされているのを確認!

まとめ

ライブラリをimportしようとしたときにcocoapodsの設定までとimport後の記事が多くて手間取ったので、
cocoapodsのインストールからライブラリをimportするところまでまとめてみました!
間違いや編集ミスあればコメントくださいmm
少しでもxcodeで環境構築する時のお役に立てればと思いますー!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
AWSS3AWSTranscribeだけあれば大丈夫です。(AWSCoreもついてくるので。)

おおまかな流れ

iOSから音声(動画)ファイルをS3にアップロード
-> iOSからTranscribeのjobを実行命令
-> AWSのTranscribeがjobを実行し、先ほどアップロードしたS3のファイルを文字化
-> Transcribeの結果取得
-> 変換結果のjsonファイルをS3からダウンロード

実装

初期設定

SceneDelegate.swift
func 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.swift
struct 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度ってことだけでしょう。
英語はしゃべれないし、発音も悪いので試してません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
AWSS3AWSTranscribeだけあれば大丈夫です。(AWSCoreもついてくるので。)

おおまかな流れ

iOSから音声(動画)ファイルをS3にアップロード
-> iOSからTranscribeのjobを実行命令
-> AWSのTranscribeがjobを実行し、先ほどアップロードしたS3のファイルを文字化
-> Transcribeの結果取得
-> 変換結果のjsonファイルをS3からダウンロード

実装

初期設定

SceneDelegate.swift
func 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.swift
struct 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度ってことだけでしょう。
英語はしゃべれないし、発音も悪いので試してません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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できる型を実装したstruct
struct SomeContentView: DependencyInjectable {
    typealias DependencyType = Dependency
    struct Dependency {
        var p: TestProtocol
    }
    var di: Dependency!

    init() {
        di = resolveDependencyInstance()
    }

    func use() {
        di.p.test()
    }
}

依存性を解決するDIコンテナを宣言します。
SomeProtocolSomeProtocolImplは何でも良いので省略します

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のように依存関係がわかりづらくなることはないと思います。

ほかに、もっと良い方法があれば教えて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift公式リファレンス色々とちょっと調べるときの@勉強メモ

Swift公式リファレンス

ちょっと公式がどれなのかわかりづらかったので...!
公式はこちら -> the swift programming language 

  • 公式リファレンスはApple公式のBooksアプリでも見れます
    BooksからSwiftを検索>最新バージョンを入手
    スクリーンショット 2020-02-19 10.15.46.png

  • Xcodeでも見れます
    Xcodeの画面上部にあるHelp>Developer Documentation
    スクリーンショット 2020-02-19 11.27.35.png
    Developer Documentationを選択すると
    スクリーンショット 2020-02-22 12.02.58.png

Xcodeのコードから調べる

Xcodeの画面上のコードからcommandを押しながらクリックする>メニュー

  • Jump to Definition メソッドや定数の定義に移動(command+click)
  • Show quick Help ヘルプを表示(option+click)
  • Edit All in Scope ファイル内のローカル変数を一括で変更する
  • Rename カーソルがあるクラスやメソッド、変数のリネームを行う

初心者でもわかりやすい

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】UICircularProgressRingを試す

UICircularProgressRingとは?

↓ こちらを見れば一目瞭然ですが、よく見かける丸型のProgressを簡単に実現できるライブラリです。

:computer:環境構築


UICircularProgressRing自体はCocoaPodsCarthageでも導入できます。
今回はCarthageで導入します。
まずは以下の内容でCartfileを作成。

github "luispadron/UICircularProgressRing"

次に以下コマンドで手元にダウンロード&ビルドします。

$ carthage update --platform iOS

Xcodeに戻って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を設定します。

ここまででライブラリの導入は完了になります。

:pencil: 実装


簡単なサンプル

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 (デフォルト ↑と同じ)
  • ontop

  • dashed ( .dashed(pattern: [7.0, 7.0]) )

  • dotted

  • bordered ( .bordered(width: 2.0, color: UIColor.red) )


中のテキストを非表示にする

以下のプロパティ shouldShowValueTextfalse に設定すれば非表示になります。

progressRing.shouldShowValueText = false


リングカラーを設定する

        progressRing.outerRingColor = UIColor.red
        progressRing.innerRingColor = UIColor.blue


開始アングルを設定する

startAngle で設定可能。デフォルトは 0 で設定されています。

  • startAngle=90

  • startAngle=180

  • startAngle=270


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秒後にリングが閉じる様に設定しています。

:link: 参考になったURL


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む