Executing text-to-speech in order - swift

I want to synthesize text. I have an array of sentences and array of pauses, that I wish between these sentences.
What was the thought
Synthesize -> start the timer, timer fires after provided time -> Synthesize -> start the timer -> Synt...
By chance, I've noticed that timer fires the lesser time first, instead of executing and setting up timers in sequence. The loop doesn't wait till synthesizer finished to pronounce, it continues to run.
How to work out that synthesizer pronounces sentences with provided pauses, and in order?
import SwiftUI
struct KingsSpeechView: View {
#ObservedObject var speaker = Speaker()
#State private var subtitles = ""
#State private var currentStepIndex = 0
let kingsSpeech = [
"Hello. Let's start the Game! Let the hunger Games Begin...Whoa-Whoa. Here're are the rules on the screen.",
"Okey, now that you know the rules, chill out. Let's play another game.",
"You say Hi, I say Ho.",
"Hooo",
"Hooo"
]
var pauses = [0.0, 20.0, 90.0, 40.0, 40.0]
// try to change into this
// var pauses = [0.0, 20.0, 10.0, 5.0, 5.0]
// the sequence of execution is completely different
// the ones that has less value, will execute first
// While I expected it to execute in order it is in array, instead it runs as it runs (wants)
// (or maybe it's the case it's just one timer for all)
// How to prevent loop from continuing to new iteration until the speech is not pronounced?
var body: some View {
VStack {
Text(subtitles)
.padding(.bottom, 50)
.padding(.horizontal, 20)
Button("Play") {
playSound()
}
}
}
func playSound() {
for step in 0..<kingsSpeech.count {
let timer = Timer.scheduledTimer(withTimeInterval: pauses[step], repeats: false) { timer in
subtitles = kingsSpeech[step]
speaker.speak("\(kingsSpeech[step])")
print("I am out")
currentStepIndex += 1
// I've tried to stop a loop from moving on, before the speech had finished to pronounce
// with some sort of a condition maybe; by index or by identifying if the synthesizer is speaking
// but it even turned out that timer executes completely different, look in time arrays above
// while speaker.semaphoreIndex == step {
// print("still waiting")
// }
// while speaker.synth.isSpeaking {
//
// }
}
}
}
}
...
import AVFoundation
import Combine
class Speaker: NSObject, ObservableObject, AVSpeechSynthesizerDelegate {
let synth = AVSpeechSynthesizer()
// started to try something with simophore, but didn't understand how to implement it
var semaphore = DispatchSemaphore(value: 0)
var semaphoreIndex = 0
override init() {
super.init()
synth.delegate = self
}
func speak(_ string: String) {
let utterance = AVSpeechUtterance(string: string)
utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
utterance.rate = 0.4
synth.speak(utterance)
}
}
extension Speaker {
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
print("all done")
semaphore.signal()
semaphoreIndex += 1
}
}

Just speak an utterance, receive the delegate method, and in that method wait the desired interval and go on to the next utterance and interval.
Here's a complete example. It uses a Cocoa project, not SwiftUI, but you can easily adapt it.
import UIKit
import AVFoundation
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class Speaker : NSObject, AVSpeechSynthesizerDelegate {
var synth : AVSpeechSynthesizer!
var sentences = [String]()
var intervals = [Double]()
func start(_ sentences: [String], _ intervals: [Double]) {
self.sentences = sentences
self.intervals = intervals
self.synth = AVSpeechSynthesizer()
synth.delegate = self
self.sayOne()
}
func sayOne() {
if let sentence = sentences.first {
sentences.removeFirst()
let utter = AVSpeechUtterance(string: sentence)
self.synth.speak(utter)
}
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
if let interval = intervals.first {
intervals.removeFirst()
delay(interval) {
self.sayOne()
}
}
}
}
class ViewController: UIViewController {
let speaker = Speaker()
override func viewDidLoad() {
super.viewDidLoad()
let sentences = [
"I will speak again in one second",
"I will speak again in five seconds",
"I will speak again in one second",
"Done"]
let intervals = [1.0, 5.0, 1.0]
self.speaker.start(sentences, intervals)
}
}

Trying to answer the question that I asked in comments to solution:
For now, it can play/ pause
TODO: Now I have to discover how to jump backward/ forward between sentences. So, for this I firstly need to stop the current speech task. speaker.synth.stopSpeaking(at: .word)
Then, I maybe should have some index tracking what's the current stage is. Then, when I stopped the task, I remember the index. And I can go backward/ forward. Now start from index-1 or index+1 place, rather than from the beginning.
#State private var isPlaying = false
...
// play button
Button(action: {
if isPlaying {
isPlaying.toggle()
speaker.synth.pauseSpeaking(at: .word)
} else {
isPlaying.toggle()
// continue playing here if it was paused before, else ignite speech utterance
if speaker.synth.isPaused {
speaker.synth.continueSpeaking()
} else {
speaker.speak()
}
}
}, label: {
Image(systemName: (isPlaying ? "pause.fill" : "play.fill"))
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
})

Related

Swift: return value every x seconds

I'm trying to generate random string every 10 seconds. I put this function in a class in another file and will call the function in another view controller. But now I'm not getting any output when I call it. How to can I fix this code
class Data{
static let instance = Data()
func randomString(of length: Int){
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var s = ""
for _ in 0 ..< length {
s.append(letters.randomElement()!)
print("\(s) = I'm in randomString Func")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) { [weak self] in
self?.randomString(of: 5)
}
}
}
in a view controller I put it under a button action and call it with this code
Button(action: {
info = Data.instance.randomString(of:5)
print(info)
}, label: {
Text ("PRINT")
.font(.callout)
.foregroundColor(Color.primary)
})
A possible solution is to use the Timer from the Combine framework:
struct ContentView: View {
#State private var text = "initial text"
#State private var timer: AnyCancellable?
var body: some View {
Text(text)
Button(action: startTimer) { // start timer on button tap, alternatively put it in `onAppear`
Text("Start timer")
}
}
func startTimer() {
// start timer (tick every 10 seconds)
timer = Timer.publish(every: 10, on: .main, in: .common)
.autoconnect()
.sink { _ in
text = DataGenerator.instance.randomString(of: 5)
}
}
}
You also need to return the String from the randomString function. A good thing would also be to rename Data to avoid collisions:
class DataGenerator { // rename the class
static let instance = DataGenerator()
func randomString(of length: Int) -> String { // return `String`
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var s = ""
for _ in 0 ..< length {
s.append(letters.randomElement()!)
print("\(s) = I'm in randomString Func")
}
return s // return String, don't call `DispatchQueue.main.async` here
}
}
You may also consider moving the timer logic out of the view like in:
How to make the View update instant in SwiftUI?

RealityKit – updateHandler of trackedRaycast not called

I call trackedRaycast() from startTracing() which is called from viewDidLoad(). My goal is to have the AR content be placed wherever the raycast from the camera hits a horizontal surface and that it updates when the raycast intersects a different location. When the user taps the screen the updates stop.
The updateHandler closure of the function trackedRaycast is never executed.
import RealityKit
import ARKit
class ViewController: UIViewController {
#IBOutlet var arView: ARView!
var gameAnchor: Experience.PreviewBoard!
var raycast: ARTrackedRaycast?
var gameIsPlaced = false
override func viewDidLoad() {
super.viewDidLoad()
setupARConfiguration()
loadGameBoard()
startTracing()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(recognizer:)))
arView.addGestureRecognizer(tapGestureRecognizer)
}
func setupARConfiguration() {
arView.automaticallyConfigureSession = false
let config = ARWorldTrackingConfiguration()
config.planeDetection = [.horizontal]
config.isCollaborationEnabled = true
config.environmentTexturing = .automatic
arView.session.run(config)
}
func loadGameBoard() {
gameAnchor = try! Experience.loadPreviewBoard()
arView.scene.addAnchor(gameAnchor)
}
func startTracing() {
raycast = arView.trackedRaycast(from: view.center, allowing: .estimatedPlane, alignment: .horizontal) { results in
print("This is never executed")
// Refine the game position with raycast update
if let result = results.first {
self.gameAnchor.setTransformMatrix(result.worldTransform, relativeTo: nil)
}
}
}
func stopTracing() {
print("raycast has stopped")
raycast?.stopTracking()
}
#objc func handleTap(recognizer: UITapGestureRecognizer) {
if !gameIsPlaced {
gameIsPlaced = true
stopTracing()
}
}
}
What could be the issue?
There appears to be some kind of lag when the view is initialized, leading to the tracked raycast not being initialized from the usual lifecycle methods. When I tried initializing it via a tap on the screen, it worked as intended. You can check this with the following code:
var r : ARTrackedRaycast? {
didSet {
print("Raycast initialized")
}
}
r = self.arView.trackedRaycast(from: self.view.center, allowing: .existingPlaneInfinite, alignment: .any) { result in
print("Callback received")
}
If you initialize your raycast from viewDidLoad() or viewDidAppear() even, the setter is never called.
The solution for me was to delay the raycast's initialization by a few seconds, like so:
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
r = self.arView.trackedRaycast(from: self.view.center, allowing: .existingPlaneInfinite, alignment: .any) { result in
print("Callback received")
}
This is, obviously, far from ideal, but right now I'm not sure how to go about solving that issue any differently.

Stopping timer at defined amount of time in Swift

I am trying to build a stopwatch which, for instance, will count to 3.0 seconds, stop, and then allow me to override the app's view with a new background/label. My issue is I cannot find a way for the timer to stop and pause on its own at 3 seconds - whenever I'd write the statement, it'd just continue counting and not do anything. Where would I put the statement in this code, and how would I write it?
import UIKit
class ViewController: UIViewController {
var time = 0.0
var timer = Timer()
#IBOutlet weak var lbl: UILabel!
#IBAction func start(_ sender: UIButton)
{
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(ViewController.action), userInfo: nil, repeats: false)
}
#IBAction func pause(_ sender: UIButton)
{
timer.invalidate()
}
#IBAction func reset(_ sender: UIButton)
{
timer.invalidate()
time = 0.0
lbl.text = ("0")
}
#objc func action()
{
time += 0.1
lbl.text = String(time)
}
}
Start by decoupling your expectations.
A "clock" is a container for the period of time from which it was started to now. Additionally, it could be "restarted", so it may need to know how long each previous run cycle was, this would then be added into the overall duration of the "clock"
The Timer is simply a way to run some code on periodical bases. Because a Timer only guarantees "at least" period, it should avoid been used for simple counter addition, as it can cause drift in your calculations (for a simple clock, it's probably not a big deal, but if you need any kind of precision, it's best to avoid it)
SimpleClock
import Foundation
public class SimpleClock {
internal var startedAt: Date? = nil
internal var totalRunningTime: TimeInterval = 0 // Used for pause/resume
var isRunning: Bool = false {
didSet {
if isRunning {
startedAt = Date()
} else {
totalRunningTime += currentCycleDuration
self.startedAt = nil
}
}
}
// This is the amount of time that this cycle has been running,
// that is, the amount of time since the clock was started to now.
// It does not include other cycles
internal var currentCycleDuration: TimeInterval {
guard let startedAt = startedAt else {
return 0
}
return Date().timeIntervalSince(startedAt)
}
func reset() {
isRunning = false
totalRunningTime = 0
}
// This is the "total" amount of time the clock has been allowed
// to run for, excluding periods when the clock was paused
var duration: TimeInterval {
return totalRunningTime + currentCycleDuration
}
}
Okay, this is pretty basic concept. It's just a container for recording when a "cycle" starts and stops and managing the "overall" duration (start/pause/resume cycles)
That's all fine and good, but what we really want is some way to determine if the period has "timeout" or not.
AlarmClock
import Foundation
class AlarmClock: SimpleClock {
var timeout: TimeInterval = 0
var hasExpired: Bool {
return duration >= timeout
}
var timeRemaining: TimeInterval {
return max(timeout - duration, 0)
}
}
All this does is add a concept of a "timeout" period and provides some additional functionality that allows use to easily determine if the clock has expired and the amount of time remaining
Example
Okay, that's all nice a good, but how does this work (and help us)
Okay, this is a really simple example. It has a label and two buttons. One button starts/pauses the clock and the other resets it.
The label displays both the running time and the remaining time of the alarm clock. If he clock expires, it will automatically be reset.
The class contains a Timer which periodically "ticks" and allows the code to inspect that current state of the alarm clock.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var durationLabel: UILabel!
#IBOutlet weak var cycleButton: UIButton!
#IBOutlet weak var resetButton: UIButton!
let alarmClock: AlarmClock = {
let clock = AlarmClock()
clock.timeout = 10.0
return clock
}()
var timer: Timer? = nil
var durationFormatter: DateComponentsFormatter {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.minute, .second]
formatter.unitsStyle = .abbreviated
return formatter
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func cycleClock(_ sender: Any) {
alarmClock.isRunning = !alarmClock.isRunning
if alarmClock.isRunning {
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
} else {
timer?.invalidate()
timer = nil
}
updateDurationLabel()
updateCycleButtonState()
}
#IBAction func restartClock(_ sender: Any) {
timer?.invalidate()
timer = nil
alarmClock.reset()
updateDurationLabel()
updateCycleButtonState()
}
func updateCycleButtonState() {
if alarmClock.isRunning {
cycleButton.setTitle("Pause", for: [])
} else {
cycleButton.setTitle("Start", for: [])
}
}
func updateDurationLabel() {
durationLabel.text = "\(durationFormatter.string(from: alarmClock.duration)!)/\(durationFormatter.string(from: alarmClock.timeRemaining)!)"
}
#objc func tick() {
print("click")
updateDurationLabel()
if alarmClock.hasExpired {
restartClock(self)
}
}
}
Now, you could also add some kind of "internal" thread to periodically check the state of the clock and call a delegate which could then bee used to update the UI, but the intention here is the decoupling of the concerns, and this means you're not adding yet another thread to the system unnecessarily (not saying you couldn't do, but it's just one more level of complexity I didn't want to add ;))

Swift: determinate NSProgressIndicator, async refreshing and waiting for return

Working in Swift3; I've got a pretty expensive operation running in a loop iterating through stuff and building it into an array that on return would be used as the content for an NSTableView.
I wanted a modal sheet showing progress for this so people don't think the app is frozen. By googling, looking around in here and not a small amount of trial and error I've managed to implement my progressbar and have it show progress adequately as the loop progresses.
The problem right now? Even though the sheet (implemented as an NSAlert, the progress bar is in the accesory view) works exactly as expected, the whole thing returns before the loop is finished.
Here's the code, hoping somebody can tell me what am I doing wrong:
class ProgressBar: NSAlert {
var progressBar = NSProgressIndicator()
var totalItems: Double = 0
var countItems: Double = 0
override init() {
progressBar.isIndeterminate = false
progressBar.style = .barStyle
super.init()
self.messageText = ""
self.informativeText = "Loading..."
self.accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
self.accessoryView?.addSubview(progressBar)
self.layout()
self.accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
self.addButton(withTitle: "")
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
self.beginSheetModal(for: ControllersRef.sharedInstance.thePrefPane!.mainCustomView.window!, completionHandler: nil)
}
}
static var allUTIs: [SWDAContentItem] = {
var wrappedUtis: [SWDAContentItem] = []
let utis = LSWrappers.UTType.copyAllUTIs()
let a = ProgressBar()
a.totalItems = Double(utis.keys.count)
a.progressBar.maxValue = a.totalItems
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
a.countItems += 1.0
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
Thread.sleep(forTimeInterval:0.0001)
DispatchQueue.main.async {
a.progressBar.doubleValue = a.countItems
if (a.countItems >= a.totalItems && a.totalItems != 0) {
ControllersRef.sharedInstance.thePrefPane!.mainCustomView.window?.endSheet(a.window)
}
}
}
}
Swift.print("We'll return now...")
return wrappedUtis // This returns before the loop is finished.
}()
In short, you're returning wrappedUtis before the asynchronous code has had a chance to finish. You cannot have the initialization closure return a value if the update process itself is happening asynchronously.
You clearly successfully diagnosed a performance problem in the initialization of allUTIs, and while doing this asynchronously is prudent, you shouldn't be doing that in that initialization block of the allUTIs property. Move this code that initiates the update of allUTIs into a separate function.
Looking at ProgressBar, it's really an alert, so I'd call it ProgressAlert to make that clear, but expose the necessary methods to update the NSProgressIndicator within that alert:
class ProgressAlert: NSAlert {
private let progressBar = NSProgressIndicator()
override init() {
super.init()
messageText = ""
informativeText = "Loading..."
accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
accessoryView?.addSubview(progressBar)
self.layout()
accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
addButton(withTitle: "")
progressBar.isIndeterminate = false
progressBar.style = .barStyle
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width:290, height: 16))
progressBar.usesThreadedAnimation = true
}
/// Increment progress bar in this alert.
func increment(by value: Double) {
progressBar.increment(by: value)
}
/// Set/get `maxValue` for the progress bar in this alert
var maxValue: Double {
get {
return progressBar.maxValue
}
set {
progressBar.maxValue = newValue
}
}
}
Note, this doesn't present the UI. That's the job of whomever presented it.
Then, rather than initiating this asynchronous population in the initialization closure (because initialization should always be synchronous), create a separate routine to populate it:
var allUTIs: [SWDAContentItem]?
private func populateAllUTIs(in window: NSWindow, completionHandler: #escaping () -> Void) {
let progressAlert = ProgressAlert()
progressAlert.beginSheetModal(for: window, completionHandler: nil)
var wrappedUtis = [SWDAContentItem]()
let utis = LSWrappers.UTType.copyAllUTIs()
progressAlert.maxValue = Double(utis.keys.count)
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
DispatchQueue.main.async { [weak progressAlert] in
progressAlert?.increment(by: 1)
}
}
DispatchQueue.main.async { [weak self, weak window] in
self?.allUTIs = wrappedUtis
window?.endSheet(progressAlert.window)
completionHandler()
}
}
}
Now, you declared allUTIs to be static, so you can tweak the above to do that, too, but it seems like it's more appropriate to make it an instance variable.
Anyway, you can then populate that array with something like:
populateAllUTIs(in: view.window!) {
// do something
print("done")
}
Below, you said:
In practice, this means allUTIs is only actually initiated when the appropriate TabViewItem is selected for the first time (which is why I initialize it with a closure like that). So, I'm not really sure how to refactor this, or where should I move the actual initialization. Please keep in mind that I'm pretty much a newbie; this is my first Swift (also Cocoa) project, and I've been learning both for a couple of weeks.
If you want to instantiate this when the tab is selected, then hook into the child view controllers viewDidLoad. Or you can do it in the tab view controller's tab​View(_:​did​Select:​)
But if the population of allUTIs is so slow, are you sure you want to do this lazily? Why not trigger this instantiation sooner, so that there's less likely to be a delay when the user selects that tab. In that case, you might trigger it the tab view controller's own viewDidLoad, so that the tab that needs those UTIs is more likely to have them.
So, if I were considering a more radical redesign, I might first change my model object to further isolate its update process from any specific UI, but rather to simply return (and update) a Progress object.
class Model {
var allUTIs: [SWDAContentItem]?
func startUTIRetrieval(completionHandler: (() -> Void)? = nil) -> Progress {
var wrappedUtis = [SWDAContentItem]()
let utis = LSWrappers.UTType.copyAllUTIs()
let progress = Progress(totalUnitCount: Int64(utis.keys.count))
DispatchQueue.global(qos: .default).async {
for uti in Array(utis.keys) {
wrappedUtis.append(SWDAContentItem(type:SWDAContentType(rawValue: "UTI")!, uti))
DispatchQueue.main.async {
progress.completedUnitCount += 1
}
}
DispatchQueue.main.async { [weak self] in
self?.allUTIs = wrappedUtis
completionHandler?()
}
}
return progress
}
}
Then, I might have the tab bar controller instantiate this and share the progress with whatever view controller needed it:
class TabViewController: NSTabViewController {
var model: Model!
var progress: Progress?
override func viewDidLoad() {
super.viewDidLoad()
model = Model()
progress = model.startUTIRetrieval()
tabView.delegate = self
}
override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) {
super.tabView(tabView, didSelect: tabViewItem)
if let item = tabViewItem, let controller = childViewControllers[tabView.indexOfTabViewItem(item)] as? ViewController {
controller.progress = progress
}
}
}
Then the view controller could observe this Progress object, to figure out whether it needs to update its UI to reflect this:
class ViewController: NSViewController {
weak var progress: Progress? { didSet { startObserving() } }
weak var progressAlert: ProgressAlert?
private var observerContext = 0
private func startObserving() {
guard let progress = progress, progress.completedUnitCount < progress.totalUnitCount else { return }
let alert = ProgressAlert()
alert.beginSheetModal(for: view.window!)
progressAlert = alert
progress.addObserver(self, forKeyPath: "fractionCompleted", context: &observerContext)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let progress = object as? Progress, context == &observerContext else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
dispatchPrecondition(condition: .onQueue(.main))
if progress.completedUnitCount < progress.totalUnitCount {
progressAlert?.doubleValue = progress.fractionCompleted * 100
} else {
progress.removeObserver(self, forKeyPath: "fractionCompleted")
view.window?.endSheet(progressAlert!.window)
}
}
deinit {
progress?.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
And, in this case, the ProgressAlert only would worry about doubleValue:
class ProgressAlert: NSAlert {
private let progressBar = NSProgressIndicator()
override init() {
super.init()
messageText = ""
informativeText = "Loading..."
accessoryView = NSView(frame: NSRect(x:0, y:0, width: 290, height: 16))
accessoryView?.addSubview(progressBar)
self.layout()
accessoryView?.setFrameOrigin(NSPoint(x:(self.accessoryView?.frame)!.minX,y:self.window.frame.maxY))
addButton(withTitle: "")
progressBar.isIndeterminate = false
progressBar.style = .barStyle
progressBar.sizeToFit()
progressBar.setFrameSize(NSSize(width: 290, height: 16))
progressBar.usesThreadedAnimation = true
}
/// Set/get `maxValue` for the progress bar in this alert
var doubleValue: Double {
get {
return progressBar.doubleValue
}
set {
progressBar.doubleValue = newValue
}
}
}
I must note, though, that if these UTIs are only needed for that one tab, it raises the question as to whether you should be using a NSAlert based UI at all. The alert blocks the whole window, and you may want to block interaction with only that one tab.

How do I make a label keep switching between two colors in Swift?

I am trying to make my label flash two colors on repeat. I want it to continuously repeat the text color Blue and Black. Is this possible? And can someone help me do this?
You can do it by using NSTimer and below is complete code for that:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var lbl: UILabel!
var timer : NSTimer?
override func viewDidLoad() {
super.viewDidLoad()
timer = NSTimer.scheduledTimerWithTimeInterval(0.4, target: self, selector: "update", userInfo: nil, repeats: true)
}
var textColorIsBlue = false
func update() {
if !textColorIsBlue {
lbl.textColor = UIColor.blueColor()
} else {
lbl.textColor = UIColor.blackColor()
}
textColorIsBlue = !textColorIsBlue
}
}
And your result will be:
And you can modify timer as per your need.
And whenever you don't need time you can stop it by using timer?.invalidate().
I couldn't help but create an alternative example
class ViewController: UIViewController {
#IBOutlet private var label: UILabel! {
didSet {
label.text = "This is just some sample text"
}
}
private var timer: dispatch_source_t?
private var generator: AlternatingColorGenerator?
deinit {
guard let timer = timer else { return }
dispatch_source_cancel(timer)
}
override func viewDidLoad() {
super.viewDidLoad()
generator = AlternatingColorGenerator(colors: [.redColor(), .blueColor(), .greenColor()])
let t = repeatingTimer(1, queue: dispatch_get_main_queue()) { [weak self] () -> Void in
self?.label.textColor = self?.generator?.next()
}
dispatch_resume(t)
timer = t
label.textColor = generator?.next()
}
}
private class AlternatingColorGenerator: GeneratorType {
var currentIndex: Int = 0
let numberOfColors: Int
let colors: [UIColor]
init(colors: [UIColor]) {
self.colors = colors
numberOfColors = colors.count
}
func next() -> UIColor? {
let color = colors[currentIndex]
currentIndex = (currentIndex + 1) % numberOfColors
return color
}
}
private func repeatingTimer(interval: NSTimeInterval, queue: dispatch_queue_t, action: () -> Void) -> dispatch_source_t {
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
let timerInterval = UInt64(interval) * NSEC_PER_SEC
let timerLeeway = timerInterval / 10
dispatch_source_set_event_handler(timer, action)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timerInterval, timerLeeway)
return timer
}
This uses a GCD timer, rather than an NSTimer, and rather than toggle the colours with a method, I've created a generator.
This uses a different programming style: one where the generation of things (timers and colours to use) is separated from the place where they are consumed.