Can't Change AVSpeechSynthesisVoice Locale With iOS TTS - ios

Using Xcode 7.2, different deployment targets (e. g. 7.1 and 9.2) and different virtual devices (e. g. iPhone 6s plus and iPhone 5s) I am not able to switch the spoken TTS language. Here is my code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
let synth = AVSpeechSynthesizer()
var myUtterance = AVSpeechUtterance()
func speak(locale: String, text: String) {
if let voice = AVSpeechSynthesisVoice(language: locale) {
myUtterance.voice = voice
// I read that the delays might cure the issue,
// but they don't. Language switching does not
// work with or without this.
myUtterance.preUtteranceDelay = 0.005
myUtterance.postUtteranceDelay = 0.005
// I read that an empty text cures the issue,
// but is doesn't. Language switching does not
// work with or without this.
for t in [" ", text] {
myUtterance = AVSpeechUtterance(string: t)
synth.speakUtterance(myUtterance)
}
} else {
debugPrint("bad voice")
}
}
override func viewDidLoad() {
super.viewDidLoad()
speak("de-DE", text: "Ich blicke auf einen blauen Himmel!")
speak("en-US", text: "This is a text, which I like very much!")
}
}
Both texts are read, but only in the device's standard language.
Furthermore I get the following error when I run the code:
2015-12-23 12:15:20.593 HFV[6993:964424] |AXSpeechAssetDownloader|error|
ASAssetQuery error fetching results (for
com.apple.MobileAsset.MacinTalkVoiceAssets) Error Domain=ASError Code=21
"Unable to copy asset information" UserInfo={NSDescription=Unable to copy
asset information}
Running the app on an iPhone 4 (not virtual, it's a real device with iOS 7.1.2) the app starts and this error does not occur, but the TTS doesn't work at all.
Thanks a lot in advance

Related

iOS 16 Focus Filter does does not show custom filter in settings

I am trying to do a POC on giving varied look to my app based on focus settings (like work, personal)
I followed this video in from WWDC 2022 https://developer.apple.com/videos/play/wwdc2022/10121/#:~:text=Focus%20was%20introduced%20in%20iOS,Focus%20or%20a%20custom%20Focus.
And tried the same.
Its supposed show the App/Custom filter in the focus settings (in settings app) as given below.
But, my app is not shown in the focus filter settings.
Using iPad for testing this and its in iOS 16 beta.
Is there anything I missed. I do not see much help elsewhere.
Code
import AppIntents
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
ExampleTestyFocusFilter().donate()
}
}
struct ExampleTestyFocusFilter : SetFocusFilterIntent
{
typealias IntentPerformResultType = IntentResult<Void,Void,Void>
#Parameter(title: "Use Dark Mode", default: false)
var alwaysUseDarkMode: Bool
#Parameter(title: "Status Message")
var status: String?
static var title: LocalizedStringResource = "Set account, status & look"
static var description: LocalizedStringResource? = """
Select an account, set your status, and configure
the look of Example Chat App.
"""
var displayRepresentation: DisplayRepresentation {
var titleList: [LocalizedStringResource] = [], subtitleList: [String] = []
if let status = self.status {
titleList.append("Status")
subtitleList.append(status)
}
titleList.append("Look")
let title = LocalizedStringResource("Set \(titleList, format: .list(type: .and))")
let subtitle = LocalizedStringResource("\(subtitleList.formatted())")
return DisplayRepresentation(title: title, subtitle: subtitle)
}
func perform() async throws -> IntentPerformResultType {
//code
return .finished
}
}
Please run the project in Xcode 14 beta 3. Had the same issue in beta 1 and now it was working fine for me.

'estimatedTimeRemaining' is only available on iOS 11.0 or newer for Progress object

I am working with Progress object from Foundation (link: https://developer.apple.com/documentation/foundation/progress)
I am doing a firmware update to a peripheral in my iOS app.
I need to have a clock going backwards to show estimated time remaining, so I wrote this code (I use rx for binding the timeRemaining value to my UILabel:
#objc func updateFirmwareProgress() {
guard let progress = firmwareProgress else {return}
if #available(iOS 11.0, *) {
timeRemaining.value = progress.estimatedTimeRemaining ?? 0.0
} else {
// Fallback on earlier versions
}
}
To my surprise, this is only supported on iOS 11. Is there a way to workaround this and get the time remaining for earlier versions?

iOS 11 Simulator not allowing LAContext and FaceID

I've running the latest Xcode 9 GM (13 Sep 2017) and have set Hardware > Face ID > Enrolled in simulator as well as Deployment Target 11.0. However I'm getting error code -6 LAErrorTouchIDNotAvailable.
Is there some setting I'm missing?
let myContext = LAContext()
let myLocalizedReasonString = "You are pretty"
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
if success {
print("// User authenticated successfully, take appropriate action")
} else {
print(" // User did not authenticate successfully, look at error and take appropriate action")
}
}
} else {
print(" // Could not evaluate policy; look at authError and present an appropriate message to user")
}
} else {
print(" // Fallback on earlier versions")
}
Face ID does not work in the Xcode 9 GM due to a framework bug. Xcode 9.1 fixes this issue.
You can test your app in the iPhone 8 simulator and ensure it works correctly with Touch ID or run the Xcode 9.1 beta and test Face ID support there.
I think the iphone X simulator's faceID doesn't work at the moment, hopefully they will fix it soon...
https://forums.developer.apple.com/thread/86779
we could do a bug report to see if it speed things along :P
https://developer.apple.com/bug-reporting
Face ID is working now, with Xcode 9.1. Follow these steps to test it in Simulator.
Add privacy statement in your target's info.plist file.
Import LocalAuthentication framework to your project and add following code to your view controller and try with Face-ID
import LocalAuthentication
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
localAuthentication()
}
func localAuthentication() -> Void {
let laContext = LAContext()
var error: NSError?
let biometricsPolicy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
if (laContext.canEvaluatePolicy(biometricsPolicy, error: &error)) {
if let laError = error {
print("laError - \(laError)")
return
}
var localizedReason = "Unlock device"
if #available(iOS 11.0, *) {
if (laContext.biometryType == LABiometryType.faceID) {
localizedReason = "Unlock using Face ID"
print("FaceId support")
} else if (laContext.biometryType == LABiometryType.touchID) {
localizedReason = "Unlock using Touch ID"
print("TouchId support")
} else {
print("No Biometric support")
}
} else {
// Fallback on earlier versions
}
laContext.evaluatePolicy(biometricsPolicy, localizedReason: localizedReason, reply: { (isSuccess, error) in
DispatchQueue.main.async(execute: {
if let laError = error {
print("laError - \(laError)")
} else {
if isSuccess {
print("sucess")
} else {
print("failure")
}
}
})
})
}
}
}
FaceID authentication will prompt you for first time to allow FaceID detection for your app.
Now enable Face ID enrolment and run your app to test Face ID simulation Testing.
Here is simulation result for matching and non-matching faces.
Result for matching face:
Result for non-matching face:
XCode 9.1 beta came out today in which the original code should work perfectly in the simulator!
According to Apples Documentation for LAContext, we need to add the key NSFaceIDUsageDescription with a reason of use String, as that will display the authorisation request for the use of FaceId on the device.
Example add this to info.plist:
NSFaceIDUsageDescription
set it to type String, and add a text that you want to be shown, in the prompt request for access to the Face ID camera.
"Your app" request your permission to use Face ID, for you to login to your account / unlock your notes / what ever reason in the end.
By adding this, you can go to the simulator for iPhone X, and you will be prompted for the Face ID, press accept, and everything should work perfectly.
Remember to enrol biometry support for the simulator by going into
Simulator -> Hardware -> Face ID / Touch ID -> Enrolled
Then you just need to pressed the Match / Non-Matching Touch / Face ID, to test out your handling
For more details and check out Apple's documentation: https://developer.apple.com/documentation/localauthentication/lacontext
---- Edit ----
This worked for me in both Xcode 9.0 and 9.1

NSLocale.current.description crashing in Xcode 9

I created a brand new single view application and added one line to the viewDidLoad method of the ViewController.swift file:
override func viewDidLoad() {
super.viewDidLoad()
_ = NSLocale.current.description
}
The NSLocale.current.description line crashes in Xcode 9 with no stack trace (just a EXC_BAD_ACCESS code=EXC_I386_GPFLT error message). The same project runs fine in Xcode 8.3.3. Anyone have any ideas why this is happening?
Here is my simulator region settings:
Based on the stack trace, it looks like it's trying to treat description as an ObjC property rather than a Swift property. Based on the source code, this shouldn't be happening. It's likely a bug in the latest Swift compiler that is producing the Swift libraries because it crashes on an iOS 9 device as well.
Be sure to file a bug with Swift since this seems to be a language bug. I've verified that it is still broken in the latest Swift 4 toolchain. In the meantime, you can get the same behavior of description using your own extension by simply duplicating the intended implementation like I've shown here.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let brokenDescription = NSLocale.current.description
// let otherBrokenDescription = Locale.current.description
let objcDescription = (Locale.current as NSLocale).debugDescription //"<__NSCFLocale: 0x1c00dbc10> \'en_US\'}"
let myDescription = Locale.current.myDescription // "en_US (current)"
}
}
extension Locale {
private var _kindDescription : String {
if self == Locale.autoupdatingCurrent {
return "autoupdatingCurrent"
} else if self == Locale.current {
return "current"
} else {
return "fixed"
}
}
public var myDescription: String {
return "\(identifier) (\(_kindDescription))"
}
public var myDebugDescription : String {
return "\(identifier) (\(_kindDescription))"
}
}
This was a bug fixed in Xcode 9 Beta 5.

Check if device supports UIFeedbackGenerator in iOS 10

In iOS 10, there is a new api which allows developers to make use of the taptic engine, UIFeedbackGenerator.
While this api is available in iOS 10, it only works on the new devices, iPhone 7 and 7 plus. It does not works on older devices including the 6S or 6S Plus, even those have a taptic engine. I guess the taptic engine on the 7 and 7 plus is a different more powerful one.
I can't seem to find a way to see if the device supports using the new api. I would like to replace some vibrate code with taptic code, where it makes sense.
Edit:
Adding the 3 concrete subclasses for search purposes:
UIImpactFeedbackGenerator
UINotificationFeedbackGenerator
UISelectionFeedbackGenerator
Edit 2:
I have a theory but no iPhone 7 device to test it so if you have one, give it a shot. UIFeedbackGenerator has a methods called prepare(). When printing out an instance of UIImpactFeedbackGenerator, I noticed that it printed a property named "prepared" which would show 0. Calling prepare() in simulator or on iPhone 6S and then printing out the instance still shows prepared as 0. Can someone call prepare() on an instance of UIImpactFeedbackGenerator from an iPhone7 and then print the instance to console to see if prepared is set to 1? This value is not exposed but there may be a way to get this info w/o using private apis.
So, apparently this can be done with a private API call.
Objective-C:
[[UIDevice currentDevice] valueForKey:#"_feedbackSupportLevel"];
Swift:
UIDevice.currentDevice().valueForKey("_feedbackSupportLevel");
... These methods seem to return:
0 = Taptic not available
1 = First generation (tested on an iPhone 6s) ... which does NOT support UINotificationFeedbackGenerator, etc.
2 = Second generation (tested on an iPhone 7) ... which does support it.
Unfortunately, there are two caveats here:
Using these could get your app rejected by Apple during the App Store's App Review, but there doesn't seem to be any other way currently.
We don't know what the actual values represent.
Special thanks to Tim Oliver and Steve T-S for helping test this with different devices. https://twitter.com/TimOliverAU/status/778105029643436033
Currently, the best way is to check the device's model using:
public extension UIDevice
public func platform() -> String {
var sysinfo = utsname()
uname(&sysinfo) // ignore return value
return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
}
}
The platform names for iPhone 7 and 7 plus are: "iPhone9,1", "iPhone9,3", "iPhone9,2", "iPhone9,4"
Source: iOS: How to determine the current iPhone/device model in Swift?
You can create a function:
public extension UIDevice {
public var hasHapticFeedback: Bool {
return ["iPhone9,1", "iPhone9,3", "iPhone9,2", "iPhone9,4"].contains(platform())
}
}
I have extended chrisamanse's answer. It extraxts the generation number from the model identifier and checks if it is equal or greater than 9. Should work with future iPhone models unless Apple decides to introduce a new internal naming scheme.
public extension UIDevice {
var modelIdentifier: String {
var sysinfo = utsname()
uname(&sysinfo) // ignore return value
return String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
}
var hasHapticFeedback: Bool {
// assuming that iPads and iPods don't have a Taptic Engine
if !modelIdentifier.contains("iPhone") {
return false
}
// e.g. will equal to "9,5" for "iPhone9,5"
let subString = String(modelIdentifier[modelIdentifier.index(modelIdentifier.startIndex, offsetBy: 6)..<modelIdentifier.endIndex])
// will return true if the generationNumber is equal to or greater than 9
if let generationNumberString = subString.components(separatedBy: ",").first,
let generationNumber = Int(generationNumberString),
generationNumber >= 9 {
return true
}
return false
}
}
Use it like so:
if UIDevice.current.hasHapticFeedback {
// work with taptic engine
} else {
// fallback for older devices
}
class func isFeedbackSupport() -> Bool {
if let value = UIDevice.current.value(forKey: "_feedbackSupportLevel") {
let result = value as! Int
return result == 2 ? true : false
}
return false
}

Resources