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

[Swift]ContainerView周りの値渡し

意外とContainerViewでの値渡しに関する記事が少なかったので、書いておきます...

親ビュー -> 子ビュー

ParentViewController.swift
//値を渡したい処理で以下を実行
let child = self.children[0] as! ChildController

child.textValue = "Hoge"
ChildController.swift
var textValue: String = "Huga"

//実行後
print(textValue)    //->Hoge

子ビュー -> 親ビュー

ParentViewController.swift
var textValue: String = "huga"

//実行後
print(textValue)    //->hoge
ChildViewController.swift
//値を渡したい処理で以下を実行
let parent = self.parent as! ParentViewController

parent.textValue = "hoge"

子ビュー -> 子ビュー

FirstViewController.swift
//値を渡したい処理で以下を実行
//一度親を通す
let parent = self.parent as! ParentViewController

let second = parent.children[0] as! SecondViewController

second.textValue = "Hoge!"
SecondViewController.swift
var textValue = "Huga?"

//実行後
print(textValue)    //-> Hoge!

最後に

子ビューを制定する、

parent.swift
let child = self.children[0] as! ChildViewController

のようなコードの数字(上のは0)はなんでもいいと思います。もしエラーが出たらその都度変えてみてください。(競合するとエラーが出る)

間違えているところがあったら教えてください。

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

[Swift]Container View周りの値渡し

意外とContainerViewでの値渡しに関する記事が少なかったので、書いておきます...

親ビュー -> 子ビュー

ParentViewController.swift
//値を渡したい処理で以下を実行
let child = self.children[0] as! ChildController

child.textValue = "Hoge"
ChildController.swift
var textValue: String = "Huga"

//実行後
print(textValue)    //->Hoge

子ビュー -> 親ビュー

ParentViewController.swift
var textValue: String = "huga"

//実行後
print(textValue)    //->hoge
ChildViewController.swift
//値を渡したい処理で以下を実行
let parent = self.parent as! ParentViewController

parent.textValue = "hoge"

子ビュー -> 子ビュー

FirstViewController.swift
//値を渡したい処理で以下を実行
//一度親を通す
let parent = self.parent as! ParentViewController

let second = parent.children[0] as! SecondViewController

second.textValue = "Hoge!"
SecondViewController.swift
var textValue = "Huga?"

//実行後
print(textValue)    //-> Hoge!

最後に

子ビューを制定する

parent.swift
let child = self.children[0] as! ChildViewController

のようなコードの数字(上のは0)はなんでもいいと思います。もしエラーが出たらその都度変えてみてください。(競合するとエラーが出る)

間違えているところがあったら教えてください。

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

TextViewの編集箇所がキーボードで隠れないようにする

このコードをコピーするだけで実装できます。

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let notificationCenter = NotificationCenter.default
            notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
            notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}

@objc func adjustForKeyboard(notification: Notification) {
        guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        let keyboardScreenEndFrame = keyboardValue.cgRectValue
        let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)

        if notification.name == UIResponder.keyboardWillHideNotification {
            textView.contentInset = .zero
        } else {
            //textViewの下にキーボードの高さ分の余白を作る
            textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
        }
        //スクロールインジゲーターが表示される長さ
        textView.scrollIndicatorInsets = textView.contentInset
        //編集箇所の高さ
        let selectedRange = textView.selectedRange
        //編集箇所が見えるようにする
        textView.scrollRangeToVisible(selectedRange)
    }

コメントで説明を書いていますが、Swiftの経験が浅いため、間違いがあるかもしれません。
英語の記事

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

【Swift5】UserDefaultsを使ったTodoアプリ

はじめに

だいぶ前に書いた前回の記事の情報が古くなり、また掲載しているコードも今ならもう少し綺麗にできるなと思ったので更新記事を書くことにしました。

注意点

UserDefaults.synchronize()について

いつの間にか公式ドキュメントにて「使うべきではない」と明示されてました。どうやらiOS12のリリースノートでも告知されていたようですね。なので本記事でも使わないことにします。

ただ、同期が少し遅い気がします。例えばチェックマークをつけて即座にキルすると次回起動時に反映されたりされてなかったりします(TODOの追加、編集、削除も同様です)。手元の環境では大体5秒くらい待てば確実にストレージに保存されてる気がします(僕のコードの問題かもしれませんが・・・)。

Todoタイトルをアラート内で記入させることについて

あまり話をややこしくしないために、前回の「新しいTodoのタイトルはアラートで記入する」という仕様はそのままにして、今回は「Todoタイトルの編集もアラートで行う」という仕様も追加しました。

ただし、このようにアラートをmodal viewの如く扱うのはHuman Interface Guidelines的にはあまりよくないかもしれません。あくまでアプリに関する重要な情報や問題を伝えるためのものであり、そもそもTODOを追加したり編集するたびにアラート(やmodal view)を表示させるのもUXとして好ましくはないでしょう。

設計

今回はなるべく「単一責務の原則」を意識しました。各オブジェクトが変更されるとしたら、その理由はただひとつでなければなりません。

UserDefaultsとアプリの間のやりとりはTodoRepositoryを介して行います。取得したTODOViewModelで保持され、view層に適宜展開されます。TODOの変更はViewModelを介してリポジトリまで通知され、ストレージに書き込まれます。

ソースコード

Todo

前回はTodoモデルをclassで実装していましたが、参照型である必要はないので今回はstructで実装します。

Todo.swift
import Foundation

struct Todo: Codable {

    static let storeKey = "Todo"

    let id: String
    let title: String
    let isDone: Bool

    init(id: String = UUID().uuidString, title: String, isDone: Bool) {
        self.id = id
        self.title = title
        self.isDone = isDone
    }
}

storeKeyはUserDefaultsに保存するためのキーで、idは個々のTODOを区別するためのものです。この辺はもしかしたら工夫の余地があるかもしれません。

TodoRepository

UserDefaultsに対するCRUD操作を担うclassです。エラーは全てguard let hoge = try? ...という形で潰しています。

TodoRepository.swift
import Foundation

final class TodoRepository {

    private let userDefaults = UserDefaults.standard
    private let jsonEncoder = JSONEncoder()
    private let jsonDecoder = JSONDecoder()

    func allTodos() -> [Todo] {
        guard let data = userDefaults.data(forKey: Todo.storeKey),
            let todos = try? jsonDecoder.decode([Todo].self, from: data) else {
            return []
        }

        return todos
    }

    func save(todos: [Todo]) {
        guard let data = try? jsonEncoder.encode(todos) else {
            return
        }

        userDefaults.set(data, forKey: Todo.storeKey)
    }
}

ViewModel

今回の設計では、TODOの追加、変更、削除はどれもcurrentTodosの変更に置き換えられ、都度リポジトリはcurrentTodosを丸ごと保存します。

ViewModel.swift
import Foundation

final class ViewModel {

    private let repository: TodoRepository

    private(set) var currentTodos: [Todo] {
        didSet {
            repository.save(todos: currentTodos)
        }
    }

    init() {
        repository = TodoRepository()
        currentTodos = repository.allTodos()
    }

    func add(todo: Todo) {
        currentTodos.append(todo)
    }

    func deleteTodo(for id: String) {
        guard let index = currentTodos.firstIndex(where: { $0.id == id }) else {
            return
        }

        currentTodos.remove(at: index)
    }

    func updateTodo(for id: String, to newTodo: Todo) {
        guard let index = currentTodos.firstIndex(where: { $0.id == id }) else {
            return
        }

        currentTodos[index] = newTodo
    }
}

Main.storyboard

こんな感じです:
Screen Shot 2020-07-31 at 11.46.23.png

以前はUITableViewControllerを使ってましたが、今回はUIViewControllerにtable viewを設置しました。どちらでも大差ないと思います。

UIAlertController+Extension

テキストフィールド付きのアラートの扱いが少し面倒くさかったので少し便利なExtensionを実装してみました。

UIAlertController+Extension.swift
import UIKit

extension UIAlertController {

    static func makeAlertWithTextField(title: String?, message: String?, textFieldConfig: ((UITextField) -> Void)?) -> UIAlertController {
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addTextField(configurationHandler: textFieldConfig)

        return alert
    }

    func addDefaultActionWithText(title: String?, textHandler: ((String) -> Void)?) -> UIAlertController {
        let action = UIAlertAction(title: title, style: .default) { [weak self] _ in
            guard let text = self?.textFields?.first?.text else {
                preconditionFailure("The Alert Controller has no text fields")
            }
            textHandler?(text)
        }
        addAction(action)
        return self
    }

    func addCancelAction() -> UIAlertController {
        addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        return self
    }

    func present(from viewController: UIViewController) {
        viewController.present(self, animated: true, completion: nil)
    }
}

後にも登場しますが、こんな感じで使うことができます:

UIAlertController
    .makeAlertWithTextField(title: "Add Todo", message: nil, textFieldConfig: { $0.placeholder = "Title" })
    .addDefaultActionWithText(title: "OK") { [weak self] text in
        guard !text.isEmpty else { return }
        self?.add(todo: Todo(title: text, isDone: false))
}
.addCancelAction()
.present(from: self)

ViewController

少し長いので分割します。

冒頭はこんな感じです:

ViewController.swift
import UIKit

final class ViewController: UIViewController {

    @IBOutlet private weak var tableView: UITableView!

    private var viewModel: ViewModel!
    private let reuseCellId = "TodoCell"

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = ViewModel()

        tableView.delegate = self
        tableView.dataSource = self
    }

    @IBAction private func onAddButtonTapped(_ sender: Any) {
        UIAlertController
            .makeAlertWithTextField(title: "Add Todo", message: nil, textFieldConfig: { $0.placeholder = "Title" })
            .addDefaultActionWithText(title: "OK") { [weak self] text in
                guard !text.isEmpty else { return }
                self?.add(todo: Todo(title: text, isDone: false))
        }
        .addCancelAction()
        .present(from: self)
    }
}

次にviewレベルでの各種TODO操作のメソッドを実装します。

ViewController.swift
private extension ViewController {

    func todo(forRowAt indexPath: IndexPath) -> Todo {
        viewModel.currentTodos[indexPath.row]
    }

    func add(todo: Todo) {
        viewModel.add(todo: todo)
        tableView.reloadData()
    }

    func updateTodo(forRowAt indexPath: IndexPath, to newTodo: Todo) {
        viewModel.updateTodo(for: todo(forRowAt: indexPath).id, to: newTodo)
        tableView.reloadData()
    }

    func deleteTodo(forRowAt indexPath: IndexPath) {
        viewModel.deleteTodo(for: todo(forRowAt: indexPath).id)
        tableView.reloadData()
    }
}

余談ですが、僕はこのようにextensionを使ってグルーピングするのが大好きです。拡張してる感が良いですね。

次にtable viewのdelegateが来ます。チェックマークの切り替えやタイトルの編集は全てTODOオブジェクトを新たに作り直しています。

ViewController.swift
extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        let selectedTodo = todo(forRowAt: indexPath)
        let newTodo = Todo(
            id: selectedTodo.id,
            title: selectedTodo.title,
            isDone: !selectedTodo.isDone
        )

        updateTodo(forRowAt: indexPath, to: newTodo)
    }

    func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let deleteAction = UIContextualAction(
            style: .destructive,
            title: "Delete"
        ) { [weak self] (_, _, _) in
            self?.deleteTodo(forRowAt: indexPath)
        }

        return .init(actions: [deleteAction])
    }

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {

        let oldTodo = todo(forRowAt: indexPath)

        let editAction = UIContextualAction(style: .normal, title: "Edit") { [weak self] (_, _, _) in
            guard let self = self else { return }

            UIAlertController
                .makeAlertWithTextField(title: "Edit Todo", message: nil, textFieldConfig: { $0.placeholder = "New Title" })
                .addDefaultActionWithText(title: "OK") { [weak self] newTitle in
                    guard !newTitle.isEmpty else { return }

                    let newTodo = Todo(id: oldTodo.id, title: newTitle, isDone: oldTodo.isDone)
                    self?.updateTodo(forRowAt: indexPath, to: newTodo)
            }
            .addCancelAction()
            .present(from: self)
        }
        editAction.backgroundColor = .systemGreen

        return .init(actions: [editAction])
    }
}

最後にdata sourceです。

ViewController.swift
extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        viewModel.currentTodos.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: reuseCellId)
        cell.textLabel?.text = todo(forRowAt: indexPath).title
        cell.accessoryType = todo(forRowAt: indexPath).isDone ? .checkmark : .none

        return cell
    }
}

もし何かあれば遠慮なく指摘していただけると幸いです。

参考

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

initとは

ググったら一発でわかりやすい記事が出てくるswiftのinitについて。

Modelでファイル作ってプロパティ定義した後にいつもこいつがいる。

initっていうんだからとりあえず初期化してんだろーなーとは勘で考えますが、なんか気になったので書きます。


【イニシャライザ】

型(クラス、構造体、列挙体)のインスタンスを初期化(initialize)する特殊なメソッドのこと

class User {
    var id: String
    var fullName: String

    init(id: String, fullName: String) {
        self.id = id
        self.fullName = fullName
    }
}

init(){}の中括弧の中で、宣言した値を初期化してます。

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

字幕をAR空間に飛ばす SFSpeechRecognizer

実装

SFSpeechRecognizerのrecognitionTaskの結果を完了ハンドラ内でSCNTextにして、SCNActionで動かします。

let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))
speechRecognizer?.delegate = self
let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

let recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { result, error in        
      //認識タスク完了時に呼ばれる
      let recognizedText = result.bestTranscription.formattedString
      let text = SCNText(string: cutText, extrusionDepth: 2)
      let textNode = SCNNode(geometry: text)
      text.firstMaterial?.diffuse.contents = UIColor.randomColor
      self.sceneView.scene.rootNode.addChildNode(textNode)
      //ランダムな位置にテキストを置いて、動かす
      let random = Float.random(in: -100...100)
      let random2 = Float.random(in: -100...100)
      let random3 = Float.random(in: -200 ... -50)
      textNode.position = SCNVector3(x: 20, y: random2, z: random3)
      textNode.runAction(SCNAction.move(to: SCNVector3(x: -100, y: random, z: random3), duration: 5),completionHandler: {
      textNode.removeFromParentNode()
      })

機械学習やAR関連の技術情報を発信しています。
MLBoysだいすけ https://twitter.com/JackdeS11
MLBoysゆうじ https://twitter.com/oka_yuuji

Core MLやCreate ML、Vision、ARKitを使ったアプリを作っています。
rockyshikoku@gmail.com

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

字幕をAR空間に飛ばす with Speech Recognizer。

コエカタマリン

Speech Recognizerで発話認識。

iOS13から、デバイス上での発話認識が可能です。

ViewController.swift
    private var speechRecognizer:SFSpeechRecognizer?
    private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
    private var recognitionTask: SFSpeechRecognitionTask?
    private var audioEngine:AVAudioEngine?

    private func speech(){

        speechRecognizer = SFSpeechRecognizer(locale: Locale.current) ?? SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))
        speechRecognizer?.delegate = self
        audioEngine = AVAudioEngine()

        //InfoPlistでスピーチの許可が必要です。

        SFSpeechRecognizer.requestAuthorization { authStatus in

            OperationQueue.main.addOperation {
                switch authStatus {
                case .authorized:
                    self.suggestAnimation("Say something")
                case .denied:
                    self.suggestAnimation("Please allow access to speech recognition")
                case .restricted:
                    self.suggestAnimation("Speech recognition restricted on this device")

                case .notDetermined:
                    self.suggestAnimation("Please allow access to speech recognition")
                default:
                    break
                }
            }
        }
        do {
            try startRecording()
        } catch {
            suggestAnimation("Could not recording")
        }
    }

    private func startRecording() throws {

        // 前のタスクがあった場合キャンセル
        recognitionTask?.cancel()
        self.recognitionTask = nil

        // オーディオセッションの定義
        let audioSession = AVAudioSession.sharedInstance()
        try audioSession.setCategory(.record, mode: .measurement, options: .duckOthers)
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
        let inputNode = audioEngine!.inputNode

        //  speech recognition request.
        recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
        guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create a SFSpeechAudioBufferRecognitionRequest object") }
        recognitionRequest.shouldReportPartialResults = true

        // デバイス上での処理が可能なのはiOS13以上。
        if #available(iOS 13, *) {
            recognitionRequest.requiresOnDeviceRecognition = false
        }

        // 認識タスクを作る。完了ハンドラ内で、テキストをARにするファンクションを。
        recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { result, error in
            var isFinal = false

            if let result = result {
                // Update the text view with the results.
                isFinal = result.isFinal
                print("Text \(result.bestTranscription.formattedString)")
                let recognizedText = result.bestTranscription.formattedString
                self.setText(recognizedText)
            }

            if error != nil || isFinal {
                // Stop recognizing speech if there is a problem.
                self.audioEngine?.stop()
                inputNode.removeTap(onBus: 0)

                self.recognitionRequest = nil
                self.recognitionTask = nil
            }
        }

        // マイクのインプットを設定。完了ハンドラ内でSppech Reconizerに音声インプットを渡す。そしてオーディオエンジンをスタート。
        let recordingFormat = inputNode.outputFormat(forBus: 0)
        inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
            self.recognitionRequest?.append(buffer)
        }

        audioEngine?.prepare()
        try audioEngine?.start()
        // Let the user know to start talking.

    }

認識結果をSCNNodeにして飛ばす

そのままだと、オーディオエンジンのスタート時からの発話全てが毎回結果として返ってきて、同じテキストが大量に返ってきたので、差分だけ表示するようにしてます。

認識結果をSCNTextにして、SCNNodeとして、SCNActionで動かしています。

ViewContoroller.swift
    private var reccentTextCount:Int = 0

    private func setText(_ result:String){
        switch speechMode {
        // ニコニコみたいに横に流す場合
        case .horizontal:
            if result.count >= reccentTextCount {
                let index = result.index(result.startIndex, offsetBy: reccentTextCount)
                let cutText = result[index...]
                reccentTextCount = result.count
                let text = SCNText(string: cutText, extrusionDepth: 2)
                text.flatness = 0.3
                text.chamferRadius = 0.3
                let textNode = SCNNode(geometry: text)
                text.firstMaterial?.diffuse.contents = UIColor.randomColor
                self.sceneView.scene.rootNode.addChildNode(textNode)
                let random = Float.random(in: -100...100)
                let random2 = Float.random(in: -100...100)
                let random3 = Float.random(in: -200 ... -50)
                textNode.position = SCNVector3(x: 20, y: random2, z: random3)
                textNode.runAction(SCNAction.move(to: SCNVector3(x: -100, y: random, z: random3), duration: 5),completionHandler: {
                    textNode.removeFromParentNode()
                })
            }

        // 雨みたいに降らす場合
        case .rain:
            if result.count >= reccentTextCount {
                if reccentTextCount  == 0 {
                    reccentTextCount = result.count
                    let text = SCNText(string: result, extrusionDepth: 5)
                    text.flatness = 0.5
                    text.chamferRadius = 0.5
                    let textNode = SCNNode(geometry: text)
                    text.firstMaterial?.diffuse.contents = UIColor.randomColor
                    self.sceneView.scene.rootNode.addChildNode(textNode)
                    let random2 = Float.random(in: -100...100)
                    let random3 = Float.random(in: -200 ... -50)
                    textNode.position = SCNVector3(x: 0, y: 100, z: random3)
                    textNode.runAction(SCNAction.move(to: SCNVector3(x: random2, y: 0, z: random3), duration: 5),completionHandler: {
                        textNode.removeFromParentNode()
                    })
                }else{
                    let randomNumber = Int.random(in: 0...(reccentTextCount - 1))
                    let randomStartIndex = result.index(result.startIndex,offsetBy: randomNumber)
                    let randomEndIndex = result.index(randomStartIndex,offsetBy: Int.random(in: 1...(result.count - randomNumber)) - 1)
                    let cutText = result[randomStartIndex...randomEndIndex]
                    reccentTextCount = result.count
                    let text = SCNText(string: cutText, extrusionDepth: 5)
                    text.flatness = 0.5
                    text.chamferRadius = 0.5
                    let textNode = SCNNode(geometry: text)
                    text.firstMaterial?.diffuse.contents = UIColor.randomColor
                    self.sceneView.scene.rootNode.addChildNode(textNode)
                    let random2 = Float.random(in: -100...100)
                    let random3 = Float.random(in: -500 ... -50)
                    textNode.position = SCNVector3(x: random2, y: 100, z: random3)
                    textNode.runAction(SCNAction.move(to: SCNVector3(x: random2, y: 0, z: random3), duration: 3),completionHandler: {
                        textNode.removeFromParentNode()
                    })
                }
            }

        // ランダム
        case .laser:
            if result.count >= reccentTextCount {
                let index = result.index(result.startIndex, offsetBy: reccentTextCount)
                let cutText = result[index...]
                reccentTextCount = result.count
                let text = SCNText(string: cutText, extrusionDepth: 2)
                text.flatness = 0.3
                text.chamferRadius = 0.3
                let textNode = SCNNode(geometry: text)
                text.firstMaterial?.diffuse.contents = UIColor.randomColor
                self.sceneView.scene.rootNode.addChildNode(textNode)
                let random = Float.random(in: -100...100)
                let random2 = Float.random(in: -100...100)
                let random3 = Float.random(in: -200 ... -50)
                textNode.position = SCNVector3(x: 20, y: random2, z: random3)
                textNode.runAction(SCNAction.move(to: SCNVector3(x: -100, y: random, z: random3), duration: 5),completionHandler: {
                    textNode.removeFromParentNode()
                })
            }
        }
    }

MLBoys Make Your World Beautiful with ML

MLBoysのTwitterをフォローしてください。お願いします。
だいすけ https://twitter.com/JackdeS11
ゆうじ https://twitter.com/oka_yuuji

Core MLやCreate ML、Vision、ARKitを使ったアプリを作っています。
エッジデバイス(iOS)で機械学習のモデルを使いたい、などありましたら、こちらのメールまでご相談ください。
rockyshikoku@gmail.com

Looks Good To Me(わるくないね)おねがいします。
            ↓
そんじゃーね。

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

字幕をAR空間に飛ばす with SFSpeechRecognizer。

実装

SFSpeechRecognizerのrecognitionTaskの結果を完了ハンドラ内でSCNTextにして、SCNActionで動かします。

let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "ja-JP"))
speechRecognizer?.delegate = self
let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()

let recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest) { result, error in        
      //認識タスク完了時に呼ばれる
      let recognizedText = result.bestTranscription.formattedString
      let text = SCNText(string: cutText, extrusionDepth: 2)
      let textNode = SCNNode(geometry: text)
      text.firstMaterial?.diffuse.contents = UIColor.randomColor
      self.sceneView.scene.rootNode.addChildNode(textNode)
      //ランダムな位置にテキストを置いて、動かす
      let random = Float.random(in: -100...100)
      let random2 = Float.random(in: -100...100)
      let random3 = Float.random(in: -200 ... -50)
      textNode.position = SCNVector3(x: 20, y: random2, z: random3)
      textNode.runAction(SCNAction.move(to: SCNVector3(x: -100, y: random, z: random3), duration: 5),completionHandler: {
      textNode.removeFromParentNode()
      })

機械学習やAR関連の技術情報を発信しています。
MLBoysだいすけ https://twitter.com/JackdeS11
MLBoysゆうじ https://twitter.com/oka_yuuji

Core MLやCreate ML、Vision、ARKitを使ったアプリを作っています。
rockyshikoku@gmail.com

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