Swift Timer only firing once in a operation [duplicate] - swift

This question already has answers here:
NSTimer callback on background thread
(2 answers)
Why would a `scheduledTimer` fire properly when setup outside a block, but not within a block?
(3 answers)
Trying to Understand Asynchronous Operation Subclass
(3 answers)
Closed 4 years ago.
I have this code set up in a subclass of an Operation in Swift. But I can't figure out why the timer is only calling the selector once.
class DogOperationRun : DogOperation {
let thisDog:Dog?
let operationID:Int?
var runDistance:Double?
var dogSpeed:Double?
var runTimeLeft:Double = 0.0
var currentRunDistance:Double = 0.0
private var interval = 0.0
private var cycle = 0.0
private var dogTimer:Timer?
init(thisDog:Dog, operationID:Int, runDistance:Double, dogSpeed:Double) {
self.thisDog = thisDog
self.operationID = operationID
self.runDistance = runDistance
self.dogSpeed = dogSpeed
super.init(self.operationID!)
}
override func main() {
guard isCancelled == false else {
operationIsFinished = true
return
}
startRun()
}
func startRun() {
operationIsExecuting = true
if let distance = runDistance, let thisDogSpeed = dogSpeed {
runTimeLeft = distance
interval = distance/thisDogSpeed
dogTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(runDog), userInfo: nil, repeats: true)
}
}
#objc func runDog() {
runTimeLeft -= interval
cycle += 1
if runTimeLeft > 0 {
runDistance = interval * cycle
print("\(self.thisDog!.name) has run \(runDistance!) meters!")
} else {
dogTimer?.invalidate()
operationIsFinished = true
}
}
}
and the superclass
import Foundation
class DogOperation: Operation {
public var id:Int
//going to over ride the existing variable and return my private one
override var isExecuting: Bool {
return operationIsExecuting
}
var operationIsExecuting = false {
didSet {
willChangeValue(forKey: "isExecuting")
print("Operation \(id) will execute")
didChangeValue(forKey: "isExecuting")
print("Operation \(id) is executing")
}
}
override var isFinished: Bool {
return operationIsFinished
}
var operationIsFinished = false {
didSet {
willChangeValue(forKey: "isFinished")
print("Operation \(id) will finish")
didChangeValue(forKey: "isFinished")
print("Operation \(id) is finished")
}
}
init(_ id:Int) {
self.id = id
}
}
calling it like so:
let lodiDogRun = DogOperationRun(thisDog: aDog, operationID: 1, runDistance: 100.0, dogSpeed: 12.0)
let pretzelDogRun = DogOperationRun(thisDog: nextDog, operationID: 2, runDistance: 100.0, dogSpeed: 14.0)
myOperationQueue.addOperations([lodiDogRun, pretzelDogRun], waitUntilFinished: true)
Edit for Matt:The only time dogRun gets called is when I force the timer with dogTimer.fire(). For the instance I captured it on it was running on thread 5:

Related

init class from another class swift

I have this ScreenSecurityWorker
final class ScreenSecurityWorker {
weak var delegate: ScreenSecurityWorkerDelegate?
private var isPassPhraseOn: Bool { return SettingsManager.shared.load(.passPhrase) }
private var isPassPhraseForced: Bool { return environment.forcePassPhraseEnabled }
var screenSecurityEnabled = false
inside my Mainrouter I am changing screenSecurityEnabled bool value to true at activateScreenSecurity
func activateScreenSecurity() {
guard !screenSecurityWorker.passwordSecurityInProgress else { return }
screenSecurityVC = ScreenSecurityBuiler.make()
app.dismissKeyboardOnTopVC()
app.window.rootViewController = screenSecurityVC
screenSecurityWorker.passPhareSuccess = false
screenSecurityWorker.screenSecurityEnabled = true
}
I am trying to reach to screenSecurityEnabled from ChatVC
but if I init it as
let screenSecurityWorker = ScreenSecurityWorker()
it has no values inside at all.
if I try like this it gives error
let screenSecurityWorker : ScreenSecurityWorker?
init(screenSecurityWorker: ScreenSecurityWorker){
self.screenSecurityWorker = screenSecurityWorker
}
I need this screenSecurityWorker.screenSecurityEnabled to change subviews in this function inside ChatVC
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if screenSecurityWorker!.screenSecurityEnabled {
let place = chatTextAreaContainerView.bounds.height + CGFloat(lastContentOffset ?? collectionView.contentOffset.y - chatTextAreaContainerView.bounds.height)
self.collectionView.contentOffset.y = place
screenSecurityWorker!.screenSecurityEnabled = false
}
}

Trying to set #published bool to true based on results from an API call

Hi first off I'm very new to swift and programing (coming from design field).
I'm trying to update doesNotificationsExist based on posts.count
I'm getting true inside the Api().getPosts {}
Where I print the following:
print("Api().getPosts")
print(doesNotificationExist)
but outside (in the loadData() {}) I still get false and not the #Publihed var doesNotificationExist:Bool = false doesn't update.
Please help me out, I would really appreciate some guidance to what I'm doing wrong and what I need to do.
Here is my code:
import SwiftUI
import Combine
public class DataStore: ObservableObject {
#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
startApiWatch()
}
func loadData() {
Api().getPosts { [self] (posts) in
self.posts = posts
if posts.count >= 1 {
doesNotificationExist = true
}
else {
doesNotificationExist = false
}
print("Api().getPosts")
print(doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func startApiWatch() {
Timer.scheduledTimer(withTimeInterval: 60, repeats: true) {_ in
self.loadData()
}
}
View where I'm trying to set an image based on store.doesNotificationsExist
StatusBarController:
import AppKit
import SwiftUI
class StatusBarController {
private var statusBar: NSStatusBar
private var statusItem: NSStatusItem
private var popover: NSPopover
#ObservedObject var store = DataStore()
init(_ popover: NSPopover)
{
self.popover = popover
statusBar = NSStatusBar.init()
statusItem = statusBar.statusItem(withLength: 28.0)
statusItem.button?.action = #selector(togglePopover(sender:))
statusItem.button?.target = self
if let statusBarButton = statusItem.button {
let itemImage = NSImage(named: store.doesNotificationExist ? "StatusItemImageNotification" : "StatusItemImage")
statusBarButton.image = itemImage
statusBarButton.image?.size = NSSize(width: 18.0, height: 18.0)
statusBarButton.image?.isTemplate = true
statusBarButton.action = #selector(togglePopover(sender:))
statusBarButton.target = self
}
}
`Other none relevant code for the question`
}
It’s a closure and hopefully the #escaping one. #escaping is used to inform callers of a function that takes a closure that the closure might be stored or otherwise outlive the scope of the receiving function. So, your outside print statement will be called first with bool value false, and once timer is completed closure will be called changing your Bool value to true.
Check code below -:
import SwiftUI
public class Model: ObservableObject {
//#Published var posts: [Post] = []
#Published var doesNotificationExist:Bool = false
init() {
loadData()
// startApiWatch()
}
func loadData() {
getPost { [weak self] (posts) in
//self.posts = posts
if posts >= 1 {
self?.doesNotificationExist = true
}
else {
self?.doesNotificationExist = false
}
print("Api().getPosts")
print(self?.doesNotificationExist)
}
print("loadData")
print(doesNotificationExist)
}
func getPost(completion:#escaping (Int) -> ()){
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) {_ in
completion(5)
}
}
}
struct Test1:View {
#ObservedObject var test = Model()
var body: some View{
Text("\(test.doesNotificationExist.description)")
}
}

How to properly connect NSColorPanel to a server to avoid overload

I'm using NSColorPanel to change the color of a view.
The color of this view is also saved in a database (Firestore).
import AppKit
class ColorPanel {
static var shared = ColorPanel()
private var stage: DB.Stage.Document? = nil
private let cp = NSColorPanel.shared
init() {
cp.setTarget(self)
cp.setAction(#selector(colorDidChange(sender:)))
cp.isContinuous = false
}
func show(stage: DB.Stage.Document) {
self.stage = stage
cp.makeKeyAndOrderFront(nil)
}
#objc func colorDidChange(sender: NSColorPanel) {
guard let stage = stage else { return }
stage.data?.color.red = Double(sender.color.redComponent)
stage.data?.color.green = Double(sender.color.greenComponent)
stage.data?.color.blue = Double(sender.color.blueComponent)
stage.update()
}
}
The problem is that I would like to set isContinuos true in order to see my view changing color real-time, but is sending too much updates to the server, so I have been forced to set it false.
There is a way to solve this? I just need to do an update when I finish the drag but I don't know how.
p.s. To call the ColorPanel in my SwiftUI view I do:
ColorPanel.shared.show(stage: stage)
Please try an approach I would use. Disclaimer: not tested due to absent Firestore setup
import Combine
class ColorPanel {
static var shared = ColorPanel()
private var stage: DB.Stage.Document? = nil
private let cp = NSColorPanel.shared
private var subscriber: AnyCancellable?
private let publisher =
PassthroughSubject<NSColor, Never>()
.throttle(for: 10, scheduler: RunLoop.main, latest: true)
init() {
cp.setTarget(self)
cp.setAction(#selector(colorDidChange(sender:)))
cp.isContinuous = true
}
func show(stage: DB.Stage.Document) {
self.stage = stage
self.subscriber = nil
if stage != nil {
self.subscriber = self.publisher
.sink { _ in
self.stage.update() // << be called once per 10 seconds
}
}
cp.makeKeyAndOrderFront(nil)
}
#objc func colorDidChange(sender: NSColorPanel) {
guard let stage = stage else { return }
stage.data?.color.red = Double(sender.color.redComponent)
stage.data?.color.green = Double(sender.color.greenComponent)
stage.data?.color.blue = Double(sender.color.blueComponent)
self.publisher.upstream.send(sender.color)
}
}

I need a reliable StopWatch in Swift 3

I need a reliable StopWatch in Swift 3. I came up with this based on another example found on SO ...
import Foundation
class StopWatch {
public private(set) var seconds:Double = 0
public var milliseconds: Double { return seconds * 1000 }
private weak var timer:Timer?
private var startTime:Double = 0
public private(set) var started = false
public func start() {
if started { return }
started = true
startTime = Date().timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) {
timer in
if !self.started { return }
self.seconds = Date().timeIntervalSinceReferenceDate - self.startTime
print("Seconds: \(self.seconds)")
}
print("Started")
}
public func stop() {
if !started { return }
timer?.invalidate()
started = false
print("Stopped")
}
}
But this doesn't seem to work reliably. Fetching seconds are often staying at 0 or in other cases when accessing seconds they don't start from 0. What is wrong with this code?
It's most likely a threading issue. If UI is involved you have to make sure that the UI related code runs on the main thread.
The variable started is redundant. Checking the timer for nil does the same.
This code adds a DispatchQueue.main block. Put your code to update the UI into this block
class StopWatch {
public private(set) var seconds:Double = 0
public var milliseconds: Double { return seconds * 1000 }
private weak var timer : Timer?
private var startTime : Double = 0
private var timerIsRunning : Bool { return timer != nil }
public func start() {
if timerIsRunning { return }
startTime = Date().timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
self.seconds = Date().timeIntervalSinceReferenceDate - self.startTime
print("Seconds: \(self.seconds)")
DispatchQueue.main.async {
// do something on the main thread
}
}
print("Started")
}
public func stop() {
if !timerIsRunning { return }
timer!.invalidate()
timer = nil
print("Stopped")
}
}

Swift SpriteKit: Thread 1: EXC_BAD_ACCESS on addChild? What am I doing wrong?

I have a game that when you die, you click and the game should restart itself, but whenever I click to restart it gives me this(see above) error. Thanks in advance(:
Here is my code:
class MCTFruitGen: SKSpriteNode {
var generationTimer: NSTimer!
var fruits = [MCTFruit]()
var fruitTracker = [MCTFruit]()
func startGeneratingFruitEvery(seconds: NSTimeInterval) {
generationTimer = NSTimer.scheduledTimerWithTimeInterval(seconds, target: self, selector: "generateFruit", userInfo: nil, repeats: true)
}
func stopGenerating() {
generationTimer?.invalidate()
}
func generateFruit() {
var scale: CGFloat
let rand = arc4random_uniform(2)
if rand == 0 {
scale = -1.0
} else {
scale = 1.0
}
let strawberry = MCTFruit()
strawberry.position.x = size.width/2 + strawberry.size.width/2
strawberry.position.y = scale * (NMCGroundHeight/160 + strawberry.size.height)
self.fruits.append(strawberry)
fruitTracker.append(fruits)
addChild(strawberry) // line that gives me the error
}
func stopFruit() {
stopGenerating()
for fruit in fruits {
fruit.stopFruitMoving()
}
}
}
Try using SKAction instead of NSTimer for generating the fruit or any timing events in SpriteKit. For example
class MCTFruitGen: SKSpriteNode {
var fruits = [MCTFruit]()
var fruitTracker = [MCTFruit]()
func startGeneratingFruitEvery(seconds: NSTimeInterval) {
let callGenerateAction = SKAction.runBlock { () -> Void in
self.generateFruit()
}
let waitOneSecond = SKAction.waitForDuration(seconds)
let sequenceAction = SKAction.sequence([callGenerateAction, waitOneSecond])
let fruitGeneratorAction = SKAction.repeatActionForever(sequenceAction)
self.runAction(fruitGeneratorAction, withKey: "fruitGenerator")
}
func stopGenerating() {
self.removeActionForKey("fruitGenerator");
}
func generateFruit() {
// Your Code
}
func stopFruit() {
stopGenerating()
// Your Code
}
}