Using NSSpeechStatusNumberOfCharactersLeft to update a progress indicator for NSSpeechSynthesizer - swift

I'm new to Swift and OS X programming. I'm trying to use a progress monitor to indicate the progress of my speech synthesizer speaking text.
let speechSynthesizer = NSSpeechSynthesizer()
speechSynthesizer.delegate = self;
speechSynthesizer.startSpeakingString(contents)
I would like to set
progressIndicator.maxValue = Double(NSSpeechStatusNumberOfCharactersLeft.characters.count)
and then periodically update the progressIndicator with NSSpeechStatusNumberOfCharactersLeft, which according to Apple's documentation should hit 0.
Every way I try to access this key, it returns the same inaccurate number, so I'm obviously not using it correctly. The only example I found was in Objective-C
NSNumber *n = [[self.speechSynth objectForProperty:NSSpeechStatusProperty error:NULL] objectForKey:NSSpeechStatusNumberOfCharactersLeft];
and I tried to translate that to Swift, but still, no dice.
let count = try speechSynthesizer.objectForProperty(NSSpeechStatusProperty).objectForKey(NSSpeechStatusNumberOfCharactersLeft)
I've also tried
speechSynthesizer.valueForKey(NSSpeechStatusNumberOfCharactersLeft))
speechSynthesizer.valueWithName(NSSpeechStatusNumberOfCharactersLeft, inPropertyWithKey: NSSpeechStatusProperty))
which throw runtime exceptions. Any thoughts? Thanks in advance!

Your second attempt is pretty close to correct:
let count = try speechSynthesizer.objectForProperty(NSSpeechStatusProperty).objectForKey(NSSpeechStatusNumberOfCharactersLeft)
Without an error message (and since I don't have a Swift 2 compiler handy) I'm guessing the reason that this fails is that the return value of objectForProperty isn't known to be an dictionary, so you can't look up values in it.
Here's my quick & dirty Swift 3 playground for testing this:
import PlaygroundSupport
import Cocoa
let synth = NSSpeechSynthesizer()
synth.startSpeaking("I'm not standing still, I am lying in wait")
// quick way to test for progress without setting up an app and delegate
PlaygroundPage.current.needsIndefiniteExecution = true
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
let statusDict = try! synth.object(forProperty: NSSpeechStatusProperty) as! [String: Any]
print(statusDict[NSSpeechStatusNumberOfCharactersLeft])
}
(Obviously, you don't need all the timer stuff or playground business if you're running this in an app with a synthesizer delegate.)
The key bit (no pun intended) is to cast the return value of objectForProperty so that Swift knows it's a dictionary, then look up the number of characters in that dictionary.
Now, this code runs correctly, but it doesn't completely work for setting a progress bar — when the synthesizer finishes speaking, NSSpeechStatusNumberOfCharactersLeft is likely to be some nonzero value. (In that case, another status key, NSSpeechStatusOutputBusy will become false.) So your progress bar will get to not quite 100%, and you can use either the NSSpeechStatusOutputBusy key or the delegate didFinishSpeaking callback to take your bar the rest of the way, remove your progress UI, or whatever.

Related

Text view's XCUIElement's value is nil in iOS 12 simulator

I have a text view whose text value is different than the empty string, which I retrieve this way:
let textViews = self.app.textViews.matching(identifier: "CommentTextView")
XCTAssertEqual(textViews.count, 1)
commentTextView = textViews.element(boundBy: 0)
XCTAssert(commentTextView.exists)
guard let text = commentTextView.value as? String else {
XCTFail()
return
}
Now this test doesn't fail on the iOS 13 simulator, and I am able to verify that the string value is correct. But on the iOS 12 simulator, the value is nil and the test fails. And it's failing also on the iOS 11 simulator. Any idea why this is failing only for iOS 12 or lower? is there any other way of retrieving the text view's value?
Update: I tried also on a real device with iOS 12, and the tests don't fail. At the moment it looks weird to me that the tests are failing only on the simulator, and I suspect that it might be a bug in the API.
Several things could be wrong here. Are you transitioning from a new screen? If so, you should wait for existence. When looking for elements in my test I write something similar to the following:
// Set globally. You can use one for api calls and the other for transition timeouts.
let defaultTimeout = 70.0
let transitionTimeout = 5.0
let commentTextView = self.app.textViews["CommentTextView"].firstMatch
let commentTextViewFound = commentTextView.waitForExistence(timeout: self.transitionTimeout)
XCTAssert(commentTextViewFound, "CommentTextView was not found.")
// Already transitioned at this point. No need to wait for existence again.
let buttonExample = self.app.buttons["ButtonExample"].firstMatch
XCTAssert(buttonExample.exists, "ButtonExample was not found.")
buttonExample.tap()
Suffer the same issue and workaround as self.app.textViews.firstMatch or self.app.textViews.element(boundBy: 0) works for me, within iOS 12.4.

NSTextField: Converting String to Int aborts (nil) outside of Xcode

I have a MacOS app that has been working fine for around a year. An NSTextField on a screen containing 20 NSTextFields started aborting with "unexpectedly found nil while unwrapping an Optional value". So far, I've done the following:
Removed and reassigned the textfield's IBOutlet link
Deleted and rebuilt the textfield
This fixes the app when I run in Xcode in my Development account. When I generate an archive and transfer it to my Production account, it still aborts.
I'm going to assume this is still the culprit, since I don't really get much from the core dump (because I probably don't understand what I'm looking at).
Here's my code. (game_number is an Int)
game_number = Int(gameNumberTextField.stringValue)!
When I split the code and do this:
let theNumber = (gameNumberTextField.stringValue)!
game_number = Int(theNumber)
theNumber is a String and correct, but game_number is nil
NSTextField parent class NSControl has a property called integerValue which returns an Int:
game_number = gameNumberTextField.integerValue
You should unwrap the optional value like this:
guard let theNumber = gameNumberTextField.integerValue else {
// Value not found, handle this case
return
}
Hope this helps!

memory usage increases dramatically after each FMDB query

Below is my source code, every time I execute the function, the memory usage increases dramatically. Please help to point out what is the problem.
func loadfontsFromDatabase(code:String)->[String] {
let documentsPath : AnyObject = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask,true)[0] as AnyObject
let databasePath = documentsPath.appending("/bsmcoding.sqlite")
let contactDB = FMDatabase(path: databasePath as String)
var c:[String]=[]
let querySQL = "SELECT FONT FROM BSMCODE WHERE BSMCODE.CODE = '\(code)' ORDER BY NO DESC"
NSLog("query:\(querySQL)")
let results:FMResultSet? = Constants.contactDB?.executeQuery(querySQL, withArgumentsIn: nil)
while (results?.next())! {
c.append((results?.string(forColumn: "FONT"))!)
}
results?.close()
return c
}
There's nothing here that would account for any substantial memory loss. I would suggest using the "Debug Memory Graph" feature in Xcode 8 to identify what objects are being created and not being released, but I suspect the problem rests elsewhere in your code. Or use Instruments to track it down what's leaking and debug from there. See https://stackoverflow.com/a/30993476/1271826.
There are unrelated issues here, though:
You are creating local contactDB, but you never open it and you never use it. It will be released when the routine exits, but it's completely unnecessary if you're going to use Constants.contactDB, anyway.
I'd advise against using string interpolation when building your SQL. Use ? placeholder and pass the code in as a parameter. This is much safer, in case the code ever contained something that couldn't be represented in SQL statement. (This is especially true if the code was supplied by the user, in which case you'd be susceptible to SQL injection attacks or innocent input errors that could lead to crashes.)
For example, you could do something like:
func loadfontsFromDatabase(code: String) -> [String] {
var c = [String]()
let querySQL = "SELECT FONT FROM BSMCODE WHERE BSMCODE.CODE = ? ORDER BY NO DESC"
let results = try! Constants.contactDB!.executeQuery(querySQL, values: [code])
while results.next() {
c.append((results.string(forColumn: "FONT"))!)
}
return c
}
If you don't like the forced unwrapping, you can do optional unwrapping if you want, but personally I'd rather know immediately when debugging during the development phase if there's some logic mistake (e.g. the contactDB wasn't open, the SQL is incorrect, etc.). But you can do optional binding and add the necessary guard statements if you want. But don't just do optional binding and silently return a value suggesting that everything is copacetic, leaving you with a debugging challenge of tracking down the problem if you don't get what you expected.
But the key point is to avoid inserting values into your SQL directly. Use ? placeholders.

Winter 2015 / Lecture 10 - Broken Twitter Package

Trying to follow along and code the Smashtag project while watching the Lecture 10 iTunes video.
When I add the dowloaded Twitter package to my Smashtag project, XCode couldn't find the Tweet class when I made reference to it in the TweetTableViewController.
Because of the problem described above, I added the four classes belonging to the Twitter package individually to the project. XCode found the four classes but adding them in this manner generated 11 compile errors.
I'm using XCode Version 6.3 (6D570) which is subsequent to the iOS 8.3 release.
Has anyone else encountered this issue?
Thank you for reading my question.
~ Lee
Possibly not the most-correct (read: best practice) way to do this, but I'm going to chalk it up to doing what it takes to finish the course.
I just went through the list of compile errors and changed the relevant properties to var instead of let. Constants can't be changed and in the new version of Swift they can only be instantiated once. So for the sake of not rewriting too much code, I chose to make certain properties vars instead of lets.
Other bugs I found following the iTunes U course:
The named ‘handler:’ argument needs the name explicitly in a few places.
The simulator will show "TwitterRequest: Couldn\'t discover Twitter account type.” until you go to Settings (inside the simulator) and set the Twitter account. At this point I had to reboot the device, as the call is made in the ViewDidLoad, and thus is only called the first time the view loads. (Alternatively, you could close out the app from the app switcher in the simulator and relaunch that way.)
Here is a gist with corrected code that you can use as a Twitter package that will work with the course and has fixes for the aforementioned bugs, minus the Twitter account setting:
https://gist.github.com/mattpetters/ccf87678ccce0c354398
As Christian R. Jimenez said, "I went to Settings in the Simulated iphone and add my Twitter Account. And everything works perfect." in http://cs193p.m2m.at/cs193p-lecture-10-table-view-winter-2015/. I just added my Twitter Account and tested it, it works!
I had similar problems with the Twitter packages using Swift 2.0 and Xcode 7.2
I'm very new to Swift, so there is a good chance the changes I made are not best practices, but the updated files do work: https://gist.github.com/awaxman11/9c48c0b4c622bffb879f.
For the most part I used Xcode's suggested changes. The two larger changes I made were:
In Tweet.swift I updated the the IndexedKeyword struct's init method to use advanceBy() instead of advance()
In TwitterRequest.swift I updated the signature of NSJSONSerialization to conform to the new error handling system
I've just had a big session fixing the Twitter package files for this same version of Xcode.
It seems that what has broken is that in this version of Swift, constants ('let x...') may only be initialized once, so if a constant is given a value in the declaration ('let x = false'), it may not be changed in the init() function. The Twitter package gives some constants initial values, but then changes the values in the init() function.
My solution to this was to follow the styles suggested in the current version of the Apple Swift language book: declare (many of) the constants as implicitly unwrapped optionals, unconditionally assign a value to them in the init() function (which value may be nil), then test whether any of them are nil, and, if so, return nil from init().
See https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html, click "On This Page" and choose "Failable Initializers"
Also, in TwitterRequest.swift, I needed to add the parameter name 'handler:' in a couple of calls to performTwitterRequest(request, handler: handler).
As an example of constant initialization, in MediaItem.swift:
<< Original Code >>
...
public let aspectRatio: Double = 0
...
init?(data: NSDictionary?) {
var valid = false
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
valid = true
}
}
}
if !valid {
return nil
}
}
...
<< Updated code >>
...
public let aspectRatio: Double
...
init?(data: NSDictionary?) {
if let urlString = data?.valueForKeyPath(TwitterKey.MediaURL) as? NSString {
if let url = NSURL(string: urlString as String) {
self.url = url
let h = data?.valueForKeyPath(TwitterKey.Height) as? NSNumber
let w = data?.valueForKeyPath(TwitterKey.Width) as? NSNumber
if h != nil && w != nil && h?.doubleValue != 0 {
aspectRatio = w!.doubleValue / h!.doubleValue
return
}
}
}
return nil
}
...

"Can't unwrap Optional.None" and I can't figure out why

This produces "fatal error: Can't unwrap Optional.None" and I don't seem to get why
var motionManager = CMMotionManager()
motionManager.accelerometerUpdateInterval = 0.2
motionManager.startAccelerometerUpdates()
var accelerationData = motionManager.accelerometerData
var accel = accelerationData.acceleration.x
If anyone can help me out, that would be great.
The issue is accelerationData is nil and you aren't checking for this. From the docs:
If no accelerometer data is available, the value of this property is nil.
You should check to make sure there is actually data before calling methods on it like this
if let accelerationData = motionManager.accelerometerData {
var accel = accelerationData.acceleration.x
}
That will ensure that if there is no data your app won't crash. Now to make sure you get some data.
You aren't getting any data because you're asking for data immediately after you initialize the core motion manager. You can show this by waiting a few seconds before checking. You can add NSThread.sleepForTimeInterval(3) right above the if let and run the project and it will enter the if let. Make sure you are using an actual device though, the simulator won't generate any motion data.