Consider the following toy example
import SwiftUI
struct WeirdExample: View {
#State private var greetings = ["Hello", "Bye"]
#State private var count = 0
#State private var greetingIndex = 0
func modifyCount() {
count = (count + 1) % 100
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150) , execute: modifyCount)
}
var body: some View {
VStack {
Spacer()
Text(greetings[greetingIndex])
Spacer()
Button("Change (maybe)"){
if count % 2 == 0 {
greetingIndex = (greetingIndex + 1) % 2
}
}
.padding()
}
.onAppear(){modifyCount()}
}
}
As far as I understand, everything in the example is running on the UI thread. However, I'm concerned by the fact that assigning to the count variable is not atomic. Is it conceivable to have a situation in which pressing the button catches that variable in a weird, undefined state? In other words, is an item dispatched to the main queue guaranteed to run uninterrupted until completion? Of course, being a serial queue guarantees that a job on the queue won't begin executing until the previous ones are finished running, but since there is a distinction between main thread and main queue, I wonder if there is reason for concern about this, or somehow the fact that everything is running on the main thread obviates any problems. Thanks.
but since there is a distinction between main thread and main queue.
The main queue is guaranteed to run on the main thread. It is also where the main RunLoop is, where #MainActor things run, and where the main OperationQueue runs, and all of these things are serialized. While there is a difference between threads and queues (and you're wise to keep that in mind), "main" is the one case where all of the relevant things are equivalent. No, there is no race condition here.
Related
I have a question regarding timers and publishers. I'm working with a timer that fires every second and does a small calculation and an update of two published values for counting down in the UI.
I've put everything that is not UI related on background threads and when the timer only runs the background code the interval is fine at 1.000 with some tolerance. But when the timer is also updating the two published values on the main thread the interval is between 1.015 and 1.019.
That doesnt sound much but after 20 minutes the total offset is at about 20 seconds.
Is there a best practice or a solution for that?
View:
#EnvironmentObject var timer: TimerData
let counter = Timer.publish(every: 1.0, tolerance: 0.2, on: .main, in: .common).autoconnect()
...
.onReceive(counter) { _ in
DispatchQueue.global(qos: .userInteractive).async {
timer.countingTime()
}
}
Model:
class TimerData: ObservableObject {
#AppStorage("time") var time: Int = 0
#AppStorage("lastAction") var lastAction: Double = 0
func countingTime() {
let now = Double(NSDate().timeIntervalSince1970)
let diff = Int(round(now - self.lastAction))
print(now - self.lastAction)
DispatchQueue.main.async {
self.lastAction = now
self.time -= diff
}
}
}
I've tried no tolerance value when creating the timer as well as a higher tolerance value. The only way I found to get the interval to the estimated 1.000 is to make "time" and "lastAction" not published (#AppStorage or #Published didnt made a difference). But I need both values published (I guess).
I am trying to code a blackjack deal button where the cards are dealt like so on hitting the button:
Deal face-up card to a player, delay 1 second
Deal face down card to a dealer, delay 1 second
Deal face-up card to a player, delay 1 second
Deal face-up card to a dealer, delay 1 second
My struggle is getting the delays in between each dealt card to make it seem more realistic as opposed to all 4 cards just appearing upon hitting the button. I currently have a deal function defined as so that is called when the deal button is hit:
for dealRound in 1...2{
var dealCard = deck.last
deck.removeLast()
Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ (_) in
player1.hand.append(dealCard!)
}
//player1.hand.append(dealCard!)
switch dealCard!.rank {
case 1:
player1.cardScore += 1
player1.hasAce = true
case 11,12,13:
player1.cardScore += 10
default:
player1.cardScore += dealCard!.rank
}
dealCard = deck.last
deck.removeLast()
Timer.scheduledTimer(withTimeInterval: 1, repeats: false){ (_) in
dealer.hand.append(dealCard!)
}
//If dealRound == 2, dealing upcard to dealer so include that in score
//Otherwise the down card is not included in the score until after the downcard is revealed
if dealRound == 2{
switch dealCard!.rank {
case 1:
dealer.cardScore += 1
dealer.hasAce = true
case 11,12,13:
dealer.cardScore += 10
default:
dealer.cardScore += dealCard!.rank
}
}
}
isDealt = true
}
However, when I hit the deal button, there is a delay and then all 4 cards appear at once. The images for the cards are defined as:
Image(dealer.hand[1].suit.rawValue + String(dealer.hand[1].rank) ).resizable().frame(width:120, height:160).offset(x: 40, y: -40)
I use dealer.hand[0] and player.hand[0] and [1] to show the other cards. The player and dealer are State variables of a Player class. I have tried using DispatchQueue.main.asyncAfter and sleep(1) but have run into similar problems. The only thing I can think to do would be to create 4 separate state Boolean variables and have them set to true after a delay (in a separate function from the deal) so the cards will appear. However, this seems like a poor way of doing it and I feel as though there may be a way similar to what I am trying but can't figure out.
In short: how would I go about adding a delay to an image appearing where the image is dependent on a #State class's array being appended to in a function call?
Scenario:
I've created an overlay window (with tag = 100) that I eventually want to dismiss. But the following code doesn't work:
UIApplication.shared.windows.first?.becomeKey()
UIApplication.shared.windows.last?.resignKey()
(lldb) po UIApplication.shared.windows
▿ 2 elements
- 0 : <UIWindow: 0x7fa698d0ad00; frame = (0 0; 768 1024); gestureRecognizers = <NSArray: 0x600000048550>; layer = <UIWindowLayer: 0x6000000278a0>>
- 1 : <UIWindow: 0x7fa698d14bb0; frame = (0 0; 768 1024); autoresize = W+H; tag = 100; gestureRecognizers = <NSArray: 0x600000252a50>; layer = <UIWindowLayer: 0x600000229660>>
Any ideas to either toggle or delete the overlay window?
Simply set the window's isHidden property to true:
var overlayWindow: UIWindow?
...
overlayWindow?.isHidden = true
overlayWindow = nil // optional
If you set any references to this window to nil, the window will be disposed.
Please also note that you are not supposed to call resignKey(). From its documentation (emphasis mine):
Discussion
Never call this method directly. The system calls this
method and posts UIWindowDidResignKey to let the window know when it
is no longer key. The default implementation of this method does
nothing, but subclasses can override it and use it to perform tasks
related to resigning the key window status.
The same is true for becomeKey(), by the way. You probably want to use makeKey() or makeKeyAndVisible().
This is essentially a two-part question.
How do I display a created UIWindow and
How do I dismiss it?
I learned that I have to keep a reference to the newly-created UIWindow rather than merely have local-scope reference. ...which is obvious.
Once I have a persistent reference to the ancillary UIWindow, I can merely assign nil to it to remove it:
var hamburgerWindow:UIWindow?
#IBAction func displayOverlayWindowAction() {
guard hamburgerWindow != nil else {
displayOverLay()
return
}
hamburgerWindow = nil
}
Ok, I am working in Swift 3 playgrounds and need to move a sprite node to a certain point ONLY when the user's mouse is down, stopping when it's released. So far I have:
override func mouseDown(with event: NSEvent) {
mouseIsDown = true
}
override func mouseDragged(with event: NSEvent) {
}
override func mouseUp(with event: NSEvent) {
mouseIsDown = false
}
func moveGuy() {
let action = SKAction.move(to: CGPoint(x: size.width / 2,y: 200), duration: 2)
action.timingMode = .easeInEaseOut
guy.run(action)
}
//UPDATE
override func update(_ currentTime: CFTimeInterval) {
if(mouseIsDown)
{
moveGuy()
}
}
This works somewhat, the problem is only after I release the mouse (mouseIsDown is false) does the SKAction actually run (smoothly). I think this is because it is being called again and again.
Normally I would use a moveBy action in little increments, but I need my node to move to a specific point.
How can I make my node move on its way to a point only when the mouse is down?
When you call
guy.run(action)
sprite-kit will run the action on the guy until completion. You're correct, moveGuy() is being called again and again (every time the frame is update, i.e. every ~33ms assuming 30fps).
Try placing moveGuy() in mouseDown(). As soon as you click, the guy will move smoothly to his destination, but he won't stop if you stop clicking. You need to somehow stop the action. What you can do is replace
guy.run(action)
with
guy.run(action, withKey: "moveGuy")
This will associate a key with your action, that you can look up later on in mouseUp():
guy.removeAction(forKey: "moveGuy")
After this, your node will move to a point only when your mouse is down. But as you've pointed out, the node's movement is still irregular if you re-click. Try changing .easeInEaseOut to .linear. The movement will then be consistent, albeit abrupt when starting/stopping.
I highly recommend the reading documentation on SKActions to gain a better understanding of how to use them.
I want to set a 800ms delay time to run a function , so I use a timer to handle it. The code is as following.But I found, at the first time, the function runs right, it just show only one console.log("here is console....."); , but when I click it again, it shows 2 consoles, and at the third click, it shows 3 consoles, and so on...
I cannot understand why this happens, can any friends explain it for me ?
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
MouseArea {
anchors.fill: parent
onClicked: {
delayTimer(800,function(){
console.log("here is console.....");
var t= Math.random();
console.log(t);
})
}
}
Timer{
id:dtimer
}
function delayTimer(delayTime, cb) {
console.log("delayTimer is starting");
dtimer.interval = delayTime;
dtimer.repeat = false;
dtimer.triggered.connect(cb);
dtimer.start();
}
}
after a few clicks, when I click it again, output is:
qml: delayTimer is starting
qml: here is console.....
qml: 0.27777099609375
qml: here is console.....
qml: 0.407012939453125
qml: here is console.....
qml: 0.60552978515625
qml: here is console.....
qml: 0.360107421875
qml: here is console.....
qml: 0.21942138671875
qml: here is console.....
qml: 0.252288818359375
qml: here is console.....
qml: 0.88134765625
qml: here is console.....
qml: 0.63092041015625
qml: here is console.....
qml: 0.5125732421875
You're connecting your signal to a slot every time you call delayTimer(), so the connections accumulate and slots are invoked multiple times. I'm not familiar with qml/js, but you need to disconnect slot after timeout is triggered:
function delayTimer(delayTime, cb) {
console.log("delayTimer is starting");
dtimer.interval = delayTime;
dtimer.repeat = false;
dtimer.triggered.connect(cb);
dtimer.triggered.connect(function(){
dtimer.triggered.disconnect(cb);
});
dtimer.start();
}
As #w1ck3dg0ph3r pointed out, you have one function bound to the signal multiple times. However, as QML assumes, all slots are executed at the same time, there is no guaranteed order, so I think, his solution might fail, if QML decides to first disconnect your function cb before it executes it.
There might be some precautions put in place, but they seem not to be too safe, as you can see in this example:
Button {
onClicked: {
clicked.connect(clicked)
console.log('here')
}
}
This results in a infinite loop on the first click, though it might be expected that the connection is only executed in the next run, and not already in the same run.
On the other hand this example:
Button {
onClicked: {
clicked.connect(function() {console.log('here'); clicked.connect(function() {console.log('there')})});
}
}
Here you might expect, from what we learned above, that both functions are executed in the first run, and the output is here and there - which is not (at least on my machine)
So the behavior when changing the bindings to a signal during the execution of those is not too well defined.
The better solution depends on your usecase. Do you want to have multiple functions bound to the signal, or always just the one?
I'd reccomend to store your function in a variable, and handle the disconnect in the onClicked-event:
Button {
property var myCB
onClicked: {
delayTimer(delayTime, cb) {
if (myCB) dtimer.triggered.disconnect(myCB)
myCB = cb
dtimer.triggered.connect(myCB)
...
}
}
}
Like this you are able to ensure that the disconnect is performed at the right time. However if the timer is restarted by some other source, the function is still bound. If you don't want this, you need to have the function it self to ensure the disconnection.
To provide a tailored solution, you would need to add more detail on what your usecase is, and what exactly you expect.