[Swift] 58 visionOSアプリ製作 その6 テキスト読み上げ / AVSpeechUtterance関連エラー

[Mac M2 Pro 12CPU, Ventura 13.6, visionOS 1.0 (21N5300a), Xcode 15.1 beta 3]

AVSpeechUtteranceなどを使ってテキスト読み上げをさせているのですが、空文字読み上げで簡単にクラッシュします。その場合はアプリ再起動で直ります。

今度は読み上げスピードを1.5に設定するとクラッシュしました。こちらは重症でシミュレータを再インストールして復活しました。

Apple Developer ForumsやStack Overflowではこのトラブルにコードで何とかしようと悪戦苦闘されていますが、そもそも環境が壊れているので、シミュレータであれば再インストールで解決する事例でした。iPhoneなど実機の場合は解決方法不明です。

import AVFoundation

let speechSynthesizer = AVSpeechSynthesizer()
let utterance: AVSpeechUtterance
var textToSpeak: String = "XXX"

utterance = AVSpeechUtterance(string: textToSpeak)
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
utterance.rate = 1.5 // これを追加するとクラッシュした

speechSynthesizer.speak(utterance)
シミュレータ選択のManage Run Destinations…から上記画面でVision Proシミュレータを削除 / 再インストール

[Swift] 57 visionOSアプリ製作 その5 SwiftDataの特徴 

[Mac M2 Pro 12CPU, Ventura 13.6, visionOS 1.0 (21N5300a), Xcode 15.1 beta 3]

SwiftDataの特徴をようやくつかめてきたので、書き留めておきます。

SwiftDataは見た目は配列っぽいですが、中身はデータベースそのものです。配列として扱うと痛い目にあいます。

SwiftDataのデータモデルにデータを追加しても、配列の末尾に配置されるとは限りません。常に何らかのキーでソートしておく必要があります。そのため通し番号あるいは日時のプロパティは必須です。

import Foundation
import SwiftUI
import SwiftData

@Model
final class Face {
    var appNum: Int
    var appName: String
    var urlScheme: String
    var createDate: String

    init(appNum: Int, appName: String, urlScheme: String, createDate: String) {
        self.appNum = appNum
        self.appName = appName
        self.urlScheme = urlScheme
        self.createDate = createDate
    }
}
import SwiftUI
import RealityKitContent
import SwiftData

@main
struct TestApp: App {
    init(){
        RotateComponent.registerComponent()
        RotateSystem.registerSystem()
        }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: [Face.self, AppColor.self])
        }
        .windowStyle(.volumetric)
        .defaultSize(width: 1.0, height: 1.2, depth: 0.3, in: .meters)

        ImmersiveSpace(id: "ImmersiveSpace") {
            ImmersiveView()
        }
        .immersionStyle(selection: .constant(.full), in: .full)
    }
}
import SwiftUI
import RealityKit
import RealityKitContent
import SwiftData

struct ContentView: View {
    // 常に生成日時で昇順ソート
    @Query(sort: \Face.createDate) public var faces: [Face]
private func updateApp() {
        var nums: [Int] = [1,2,3,4,5,6]
        
        let size = faces.count
        var index = size - 1
        print("size: \(size)")

        // 配列の末尾からfor文を回す(新しいデータのみ残すため)
        for face in faces.reversed() {
            print("index: \(index)")
            print("face.appNum: \(face.appNum)")
            print("face.appName: \(face.appName)")
            print("face.createDate: \(face.createDate)")
            
            let surNum = face.appNum
            
            if nums != [], nums.contains(surNum){
                nums.removeAll { $0 == surNum }
                print("\(surface.appNum)をnumsから削除しました")
                print("現在のnums: \(nums)")
                print("\(surface.appNum)の最初のsurfaceは残留しました")
                
            } else {
                context.delete(faces[index])
                try? context.save()
                print("index\(index)を削除しました")
            }
            index -= 1
            
            if index == -1 {
                print("faces整理後確認")
                var count = 0
                for face in faces{
                    print("faces整理後 count: \(count)")
                    print("face.appNum: \(face.appNum)")
                    print("face.appName: \(face.appName)")
                    print("face.urlScheme: \(face.urlScheme)")
                    print("face.createDate: \(face.createDate)")
                    
                    count += 1
                }
            }
        }
    }