Ios 在SwiftUI中,一次只允许一个OnTap手势输入吗?

Ios 在SwiftUI中,一次只允许一个OnTap手势输入吗?,ios,swift,iphone,xcode,swiftui,Ios,Swift,Iphone,Xcode,Swiftui,我是Swift的新手,请原谅我对这个话题的无知。我试图写一个简单的井字游戏作为学习Swift的个人项目,但我一直遇到一个问题,如果用户在同一时间击中多个空格,游戏将在两个位置放置“X” 我将其设置为,一旦用户点击一个空格,一个名为“playerTurn”的变量将切换为false,以防止用户点击另一个点,直到AI完成轮次,此时,它将playerTurn切换回true。切换发生在“updateBlock()”函数中。每次用户点击空格时,if语句都会检查是否轮到玩家 问题是,如果用户在正确的时间点击两

我是Swift的新手,请原谅我对这个话题的无知。我试图写一个简单的井字游戏作为学习Swift的个人项目,但我一直遇到一个问题,如果用户在同一时间击中多个空格,游戏将在两个位置放置“X”

我将其设置为,一旦用户点击一个空格,一个名为“playerTurn”的变量将切换为false,以防止用户点击另一个点,直到AI完成轮次,此时,它将playerTurn切换回true。切换发生在“updateBlock()”函数中。每次用户点击空格时,if语句都会检查是否轮到玩家

问题是,如果用户在正确的时间点击两个或多个空格,两个空格中的函数都将执行,因为它没有足够的时间将playerTurn切换为false。我有下面的一段代码。我确信我遗漏了一些明显的东西,但我似乎无法找到如何让它按我需要的方式工作。非常感谢您的帮助

HStack {
    Spacer()
    Image(iconArray[blockValue[0]])
        .resizable()
        .aspectRatio(1, contentMode: .fit)
        .background(Color.accentColor)
        .cornerRadius(20)
        .scaleEffect(0.9)
        .onTapGesture {
            if playerTurn == true && zeroBlocksExist[0] == true {
                // Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
                updateBlock(blockPosition: 0)
            }
        }
    Image(iconArray[blockValue[1]])
        .resizable()
        .aspectRatio(1, contentMode: .fit)
        .background(Color.accentColor)
        .cornerRadius(20)
        .scaleEffect(0.9)
        .onTapGesture {
            if playerTurn == true && zeroBlocksExist[1] == true {
                // Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
                updateBlock(blockPosition: 1)
            }
        }
    Image(iconArray[blockValue[2]])
        .resizable()
        .aspectRatio(1, contentMode: .fit)
        .background(Color.accentColor)
        .cornerRadius(20)
        .scaleEffect(0.9)
        .onTapGesture {
            if playerTurn == true && zeroBlocksExist[2] == true {
                // Ensure that it is the player's turn and that the space is vacant before updating the space with the new value
                updateBlock(blockPosition: 2)
            }
        }
    Spacer()
}

// updateBlock function 

func updateBlock(blockPosition: Int) {
        DispatchQueue.global().async {
            if movesCount < 9 {
                if playerTurn == true {
                    playerTurn.toggle() // Disable player turn
                    titleText = "CPU's turn"
                    restartButtonState.toggle() // Disable the restart button until after the CPU does its turn
                    blockValue[blockPosition] = 1 // Put the X icon in the requested space
                    if allowSounds {
                        playAudio(soundName: "pop-1", soundExt: "mp3") // Play the pop sound effect
                    }
                    checkVacantBlocks() // Check vacant blocks and update the array
                } else if playerTurn == false {
                    blockValue[blockPosition] = 2 // Put the O icon
                    playerTurn.toggle()
                    titleText = "Your turn"
                    restartButtonState.toggle() // Enable the restart button
                    checkVacantBlocks()
                }
                movesCount += 1
                print("Moves made: ", String(movesCount))
            } else if movesCount >= 9 {
                print("Game board full!")
                checkWinnings()
            }
            return
        }
        
    }
HStack{
垫片()
图像(iconArray[blockValue[0]])
.可调整大小()
.aspectRatio(1,contentMode:.fit)
.background(Color.accentColor)
.转弯半径(20)
.scaleEffect(0.9)
.ontapsigne{
如果playerTurn==true&&zeroBlocksExist[0]==true{
//在使用新值更新空间之前,请确保轮到玩家,并且空间是空的
updateBlock(块位置:0)
}
}
图像(iconArray[blockValue[1]])
.可调整大小()
.aspectRatio(1,contentMode:.fit)
.background(Color.accentColor)
.转弯半径(20)
.scaleEffect(0.9)
.ontapsigne{
如果playerTurn==true&&zeroBlocksExist[1]==true{
//在使用新值更新空间之前,请确保轮到玩家,并且空间是空的
updateBlock(块位置:1)
}
}
图像(iconArray[blockValue[2]])
.可调整大小()
.aspectRatio(1,contentMode:.fit)
.background(Color.accentColor)
.转弯半径(20)
.scaleEffect(0.9)
.ontapsigne{
如果playerTurn==true&&zeroBlocksExist[2]==true{
//在使用新值更新空间之前,请确保轮到玩家,并且空间是空的
updateBlock(块位置:2)
}
}
垫片()
}
//updateBlock函数
func updateBlock(块位置:Int){
DispatchQueue.global().async{
如果移动量<9{
如果playerTurn==true{
playerTurn.toggle()//禁用玩家回合
titleText=“轮到CPU了”
restartbutonstate.toggle()//禁用重启按钮,直到CPU轮到它为止
blockValue[blockPosition]=1//将X图标放在请求的空间中
如果允许{
playAudio(soundName:“pop-1”,soundExt:“mp3”)//播放pop音效
}
checkVacantBlocks()//检查空块并更新数组
}否则,如果playerTurn==false{
blockValue[blockPosition]=2//放置O图标
playerTurn.toggle()
titleText=“轮到你了”
restartButtonState.toggle()//启用重新启动按钮
checkVacantBlocks()
}
MoveScont+=1
打印(“移动:”,字符串(moveScont))
}如果MoveScont>=9,则为else{
打印(“游戏板已满!”)
支票赢款()
}
返回
}
}

除非在
updateBlock
中使用类似于
DispatchQueue
的东西,否则这似乎是不可能的。我假设在它里面,您在某个点将
playerTurn
更改为false。由于UI代码在单个主线程上运行,因此不应该出现两个点击都可以读取
playerTurn
true
并同时执行其代码的情况,因为这将发生在同一个线程上。但是,同样,这是假设您在
updateBlock
@jnpdx期间没有进行调度。我在updateBlock()函数中使用DispatchQueue.global().async,以便它可以更新UI。如果它不在那里,UI将根本不会更新。是的,updateBlock函数中的第一件事是,如果发现playerTurn当前设置为true,则将其切换为false。您必须有两个分派调用。由于
ontapsignate
而被调用的任何内容都将位于主线程上。但是,这证实了您在我们的示例中没有包含足够的代码来诊断此问题。您必须通过在线程之间移动来引入竞争条件,您给出的代码中没有显示任何竞争条件。@jnpdx我刚刚用用户点击空格时调用的函数更新了代码。在CheckVacanBlocks func执行之后,它会运行另一个名为checkWinnings的func,如果游戏还没有结束,它将使用AI选择的空间重新执行updateBlocks func。我建议删除DispatchQueue块,并仅在需要进行计算昂贵的操作时使用它。没有理由在不同的队列中执行到目前为止列出的任何代码——保持轻松,保持在主线程和相同的运行循环中。