I have run in to a slight problem when trying to manage multiple NSTimers in SWIFT. No matter what I Try it will only ever invalidate the last timer I create. But I need to be able to invalidate any of the timers by choice.
Is there some way to create a reference ID that I could then use to select the specific NSTimer I want to invalidate? Any help would be great.
here is a snippet of my code.
import UIKit
var myTimer:NSTimer!
class TimerManager: NSObject {
}
public class Timer {
// each instance has it's own handler
private var handler: (timer: NSTimer) -> () = { (timer: NSTimer) in }
public class func start(duration: NSTimeInterval, repeats: Bool, handler:(timer: NSTimer)->()) {
var t = Timer()
t.handler = handler
myTimer = NSTimer.scheduledTimerWithTimeInterval(duration, target: t, selector: "processHandler:", userInfo: nil, repeats: repeats)
}
#objc private func processHandler(timer: NSTimer) {
self.handler(timer: timer)
}
}
class countdown{
//handles the countdown for the timer and invalidates when it reaches 0
var y = 0
func begin(length:Int) {
y = length
let delta = 1
let delay = 1.0
Timer.start(delay, repeats: true) {
(t: NSTimer) in
self.y -= delta
println(self.y)
if (self.y <= 0) {
t.invalidate()
}
}
}
func end () {
println("timer stopped")
myTimer.invalidate()
}
}
And I create the timer like so:
countdownTimer.begin(120) //time length in seconds ... 120 = 2 mins
To stop timer:
countdownTimer.end()
You may create a dictionary retaining NSTimer objects. Note that timerManager needs to be defined at global scope. Hope it shed light.
class TimerManager {
var _timerTable = [Int: NSTimer]()
var _id: Int = 0
/*! Schedule a timer and return an integer that represents id of the timer
*/
func startTimer(target: AnyObject, selector: Selector, interval: NSTimeInterval) -> Int {
var timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: target, selector: selector, userInfo: nil, repeats: true)
_id += 1
_timerTable[_id] = timer
return _id
}
/*! Stop a timer of an id
*/
func stopTimer(id: Int) {
if let timer = _timerTable[id] {
if timer.valid {
timer.invalidate()
}
}
}
/*! Returns timer instance of an id
*/
func getTimer(id: Int) -> NSTimer? {
return _timerTable[id]
}
}
// This needs to be delcared at global scope, serving as "singleton" instance of TimerManager
let timerManager = TimerManager()
Following code creates a new timer.
var aTimer = timerManager.startTimer(self, selector: Selector("timerFunction"), interval: 1)
To stop the timer, simply pass id to stopTimer(id: Int).
/* Code ... */
timerManager.stopTimer(aTimer)
Also note getTimer method returns the actual instance with an id.
Regards
Related
I am running into an error:
Fatal error: Index out of range
I would like to know what is out of range since I have 6 values printed in the console and 6 in my JSON that I created. This is happening when I am navigating to the last item.
(I already tried similar questions/answers from StackoverFlow with no success).
enter image description here
`
import SwiftUI
public struct Stepper: View {
public enum Step: Equatable {
case fixed, animated(duration: Int)
}
#Binding var selected: Int
let steps: [Step]
#State var timer: Timer?
public init(selected: Binding<Int>, steps: [Step]) {
self._selected = selected
self.steps = steps
}
public var body: some View {
HStack {
ForEach(steps.indices) { item in
StepView(step: steps[item], index: item, selected: $selected)
}
}
.padding(.horizontal,16)
.padding(.vertical,12)
.onChange(of: selected,perform: updateTimer)
.onAppear{
updateTimer(newValue: 0)
}
}
func updateTimer(newValue: Int) {
timer?.invalidate()
guard case .animated(let duration) = steps[newValue] else {
return
} **// the app is crashing here in the guard let **
timer = Timer.scheduledTimer(withTimeInterval: Double(duration),
repeats: false,
block: { _ in
if steps.count > selected + 1 {
withAnimation {
selected += 1
}
}
})
}
}
`
I already tried to update the timer with different values (newValue: ) and to pass item instead of newValue with no success.
ForEach isn't a for loop, it's a View that needs to be supplied with Identifiable data (so it can track changes), e.g.
ForEach(steps) { step in
How often and in what order the closure is called depends on what kind of subviews are used in it.
You also need #State var steps and struct Step: Identifiable
Suppose you have an array, and you want to iterate over each element in the array and call a function obj.f which accepts that element as a parameter.
f is asynchronous, and completes nearly instantly, but it invokes a callback handler found in obj.
What would be the best way to match each element only after the previous finishes?
Here is one way:
let arr = ...
var arrayIndex = 0
var obj: SomeObj! // Required
obj = SomeObj(handler: {
...
arrayIndex += 1
if arrayIndex < arr.count {
obj.f(arr[arrayIndex])
}
})
obj.f(arr[0]) // Assumes array has at least 1 element
This works fine, but isn't ideal.
I could use a DispatchSemaphore, but that isn't great because it blocks the current thread.
Also, the reason why each operation must run only when the previous has finished is because the api I'm using requires it (or it breaks)
I was wondering if there was a better/more elegant way to accomplish this?
You say:
Suppose you have an array, and you want to iterate over each element in the array and call a function ... which accepts that element as a parameter.
The basic GCD pattern to know when a series of asynchronous tasks are done is the dispatch group:
let group = DispatchGroup()
for item in array {
group.enter()
someAsynchronousMethod { result in
// do something with `result`
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
// note, don't use the results here, because the above all runs asynchronously;
// return your results in the above `notify` block (e.g. perhaps an escaping closure).
If you wanted to constrain this to, say, a max concurrency of 4, you could use the non-zero semaphore pattern (but make sure you don't do this from the main thread), e.g.
let group = DispatchGroup()
let semaphore = DispatchSemaphore(value: 4)
DispatchQueue.global().async {
for item in array {
group.enter()
semaphore.wait()
someAsynchronousMethod { result in
// do something with `result`
semaphore.signal()
group.leave()
}
}
group.notify(queue: .main) {
// what to do when everything is done
}
}
An equivalent way to achieve the above is with a custom asynchronous Operation subclass (using the base AsynchronousOperation class defined here or here), e.g.
class BarOperation: AsynchronousOperation {
private var item: Bar
private var completion: ((Baz) -> Void)?
init(item: Bar, completion: #escaping (Baz) -> Void) {
self.item = item
self.completion = completion
}
override func main() {
asynchronousProcess(bar) { baz in
self.completion?(baz)
self.completion = nil
self.finish()
}
}
func asynchronousProcess(_ bar: Bar, completion: #escaping (Baz) -> Void) { ... }
}
Then you can do things like:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4
let completionOperation = BlockOperation {
// do something with all the results you gathered
}
for item in array {
let operation = BarOperation(item: item) { baz in
// do something with result
}
operation.addDependency(completionOperation)
queue.addOperation(operation)
}
OperationQueue.main.addOperation(completion)
And with both the non-zero semaphore approach and this operation queue approach, you can set the degree of concurrency to whatever you want (e.g. 1 = serial).
But there are other patterns, too. E.g. Combine offers ways to achieve this, too https://stackoverflow.com/a/66628970/1271826. Or with the new async/await introduced in iOS 15, macOS 12, you can take advantage of the new cooperative thread pools to constrain the degree of concurrency.
There are tons of different patterns.
you could try using swift async/await, something like in this example:
struct Xobj {
func f(_ str: String) async {
// something that takes time to complete
Thread.sleep(forTimeInterval: Double.random(in: 1..<3))
}
}
struct ContentView: View {
var obj: Xobj = Xobj()
let arr = ["one", "two", "three", "four", "five"]
var body: some View {
Text("testing")
.task {
await doSequence()
print("--> all done")
}
}
func doSequence() async {
for i in arr.indices { await obj.f(arr[i]); print("--> done \(i)") }
}
}
I want my swift code to call the playSound and play the sound based for the duration of each item in array playamount. So I want the user to play the sound for the first time for 10 seconds then play the sound starting at the beginning for 20 and then the same thing for 30 seconds. So the sound always starts at the beginning each time it is called.
import UIKit; import AVFoundation
class ViewController: UIViewController {
var player: AVAudioPlayer?
func playSound() {
let url = Bundle.mainBundle().URLForResource("rock", withExtension: "mp3")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
}
var playAmount : [Int] = [10,20,30]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
class ViewController: UIViewController {
var player: AVAudioPlayer?
var currentIndex: Int = 0
func runMusicBox() {
guard !playAmount.isEmpty else { return }
currentIndex = 0
newTimerForIndex(index: 0)
}
func newTimerForIndex(index: Int) {
player?.prepareToPlay()
player?.play()
Timer.scheduledTimer(withTimeInterval: Double(playAmount[index]), repeats: false) { timer in
self.player?.stop()
if self.currentIndex + 1 < self.playAmount.count {
self.currentIndex += 1
self.newTimerForIndex(index: self.currentIndex)
} else {
self.player?.stop()
}
}
}
func playSound() {
let url = Bundle.mainBundle().URLForResource("rock", withExtension: "mp3")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
runMusicBox()
} catch let error as NSError {
print(error.description)
}
}
var playAmount : [Int] = [10,20,30]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
Hi, can you try this code. Here I have a timer, and when one stops, then we check if we have another element in the array and will run a new timer. The Timer is working, but i didn;t check the player. if it works as expected - it should work for you
Hello I am trying to take an array I have and update a label to display each element in the array one second apart. An example would be me having the array [3,6,2] and my label would show 3 then wait a second and show 6 then wait a second and show 2. I've seen many NSTimer examples with update functions doing things like this but only with an incrementation on a number, never trying to parse an array. Can anyone help?
Update
I am calling my timer in a UIButton and am running into a problem. The timer works fine but my code in the button function under my timer runs before the timer and update function. My code below generates a random array of numbers then should display them one second apart. It is doing this correct but my print statement under my timer is running before it updates and displays the numbers in the textbook. I do not know why? Is the timer running on a different thread?
func updateCountdown() {
if(numbersIndex <= numberLimit){
self.counter.text = String(numbers[numbersIndex])
}else if(numbersIndex == numberLimit+1){
self.counter.text = ""
}else{
timer!.invalidate()
timer=nil
}
numbersIndex+=1
}
#IBAction func startButton(_ sender: AnyObject){
startB.setTitle("", for: .normal)
var highestLength = 3
//for trialNumber in 0...11{
var numberSequence = Array(repeating:11, count: highestLength)
for count in 0...highestLength-1{
var randomNumber = Int(arc4random_uniform(10))
if(count>0){
while(numberSequence.contains(randomNumber) || randomNumber-1 == numberSequence[count-1]){
randomNumber = Int(arc4random_uniform(10))
}
}
numberSequence[count] = randomNumber
}
print(numberSequence)
self.numbers = numberSequence
self.numbersIndex = 0
self.numberLimit = numberSequence.count-1
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateCountdown), userInfo: nil, repeats: true)
print("this should be last")
//do other stuff too
//}
}
you can do something like this.
var counter = 0
var list: [String] = ["Eggs", "Milk"]
override func viewDidLoad() {
super.viewDidLoad()
var timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
func update() {
label.text = "\(list[counter])"
counter += 1
if (counter >= list.count) {
counter = 0
}
}
this is assumming the name of you Label is label
I am new in swift language and my problem is about how to use observable/observer pattern in swift.
I want to make my array to be observable in my SocketManager class so it can be observed by my UIViewController class. I have used the Observable class written by Andrew J Wagner which I got from this link:
http://www.drewag.me/posts/swift-kvo-substitute-observable-variables
I have the array:
var marketIndexList: Array< MarketIndex > = []
which will get its data from a server. This list will be updated every time a new data received from server. After I got the values of my Array from server I want to make it of type Observable class which is implemented by the above link:
marketIndexList = Observable(marketIndexList)
But I got this error:
'MarketIndex' is not identical to 'AnyObject'
MarketIndex is a class of type NSObject which has some properties of type String.
This is the Observable class that I have used:
import Foundation
class Observable {
typealias DidChangeHandler = (oldValue: Array<MarketIndex>?, newValue: Array<MarketIndex>) -> ()
var value : Array<MarketIndex> = [] {
didSet {
for (owner, handlers) in self.observers {
for handler in handlers {
handler(oldValue: oldValue, newValue: value)
}
}
}
}
init(_ value: Array<MarketIndex>) {
self.value = value
}
func addObserverForOwner(owner: IndexViewController, triggerImmediately: Bool, handler: DidChangeHandler) {
if let index = self.indexOfOwner(owner) {
// since the owner exists, add the handler to the existing array
self.observers[index].handlers.append(handler)
} else {
// since the owner does not already exist, add a new tuple with the
// owner and an array with the handler
self.observers.append(owner: owner, handlers: [handler])
}
if (triggerImmediately) {
// Trigger the handler immediately since it was requested
handler(oldValue: nil, newValue: self.value)
}
}
func removeObserversForOwner(owner: AnyObject) {
if let index = self.indexOfOwner(owner) {
self.observers.removeAtIndex(index)
}
}
// #pragma mark - Private Properties
var observers: [(owner: IndexViewController, handlers: [DidChangeHandler])] = []
// #pragma mark - Private Methods
func indexOfOwner(owner: AnyObject) -> Int? {
var index : Int = 0
for (possibleOwner, handlers) in self.observers {
if possibleOwner === owner {
return index
}
index++
}
return nil
}
}
Can anyone tell me what the problem is?
Also does anyone know a way to observe an array of objects in swift?
I would appreciate any help.
Thanks in advance.
The error is because marketIndexList is defined as Array<MarketIndex> but you assigned Observable instance. Perhaps you wanted to do something like this:
var observableList: Observable = Observable([])
var marketIndexList: Array<MarketIndex> = [MarketIndex(), MarketIndex()]
observableList.value = marketIndexList
// Or maybe
observableList = Observable(marketIndexList)
By the way, you can also use Objective-C KVO from Swift. Just mark the property as dynamic and make sure the class inherits NSObject to make the property observable. For example:
class ObservableClass: NSObject {
dynamic var value = [Int]()
}
This post is good to read for KVO in Swift in addition to what you referred to.
https://medium.com/proto-venture-technology/the-state-of-kvo-in-swift-aa5cb1e05cba