简体   繁体   English

Escaping 闭包捕获变异的“自我”参数 Swift 中的错误

[英]Escaping closure captures mutating 'self' parameter Error in Swift

Creating a simple card game (Set) and I have a function in the model that deals X cards onto the deck.创建一个简单的纸牌游戏(套装),我在 model 中有一个 function,它将 X 牌放到牌组上。 Currently, when I click the deal card button they all show up at once so I added the timer so that they would appear one after another.目前,当我单击交易卡按钮时,它们都会同时显示,因此我添加了计时器,以便它们一个接一个地出现。 This gives the error "Escaping closure captures mutating 'self' parameter" Any ideas on what I can fix?这给出了错误“转义闭包捕获变异'self'参数”关于我可以修复什么的任何想法?

mutating func deal(_ numberOfCards: Int) {
        for i in 0..<numberOfCards {
            Timer.scheduledTimer(withTimeInterval: 0.3 * Double(i), repeats: false) { _ in
                if deck.count > 0 {
                    dealtCards.append(deck.removeFirst())
                }
            }
        }    
}

A timer is not even required.甚至不需要计时器。 You can use transitions in combination with an animation to get the desired effect.您可以将转换与 animation 结合使用以获得所需的效果。 Here the transition is delayed based on the index of the card:这里的转换是根据卡片的索引延迟的:

class CardModel: ObservableObject {
    @Published var cards: [Int] = []
    
    func deal(_ numberOfCards: Int) {
        cards += (cards.count ..< (cards.count + numberOfCards)).map { $0 }
    }
    
    func clear() {
        cards = []
    }
}

struct ContentView: View {

    @ObservedObject var cardModel = CardModel()
    
    var body: some View {
        
        GeometryReader { geometry in
            VStack {
                HStack {
                    ForEach(0 ..< self.cardModel.cards.count, id: \.self) { index in
                        CardView(cardNumber: self.cardModel.cards[index])
                            .transition(.offset(x: geometry.size.width))
                            .animation(Animation.easeOut.delay(Double(index) * 0.1))
                    }
                }
                
                Button(action: { self.cardModel.deal(2) }) {
                    Text("Deal")
                }
                
                Button(action: { self.cardModel.clear() }) {
                    Text("Clear")
                }
            }
        }
    }
}

struct CardView: View {
    let cardNumber: Int
    
    var body: some View {
        Rectangle()
            .foregroundColor(.green)
            .frame(width: 9, height: 16)
    }
}

Or a bit simpler (without the CardModel):或者更简单一点(没有 CardModel):

struct ContentView: View {

    @State var cards: [Int] = []
    
    func deal(_ numberOfCards: Int) {
        cards += (cards.count ..< (cards.count + numberOfCards)).map { $0 }
    }
    
    func clear() {
        cards = []
    }
    
    var body: some View {
        
        GeometryReader { geometry in
            VStack {
                HStack {
                    ForEach(0 ..< self.cards.count, id: \.self) { index in
                        CardView(cardNumber: self.cards[index])
                            .transition(.offset(x: geometry.size.width))
                            .animation(Animation.easeOut.delay(Double(index) * 0.1))
                    }
                }
                
                Button(action: { self.deal(2) }) {
                    Text("Deal")
                }
                
                Button(action: { self.clear() }) {
                    Text("Clear")
                }
            }
        }
    }
}

struct CardView: View {
    let cardNumber: Int
    
    var body: some View {
        Rectangle()
            .foregroundColor(.green)
            .frame(width: 9, height: 16)
    }
}

Note this approach works fine if you're centering the cards, because the previous cards will need to shift too.请注意,如果您将卡片居中,这种方法可以正常工作,因为之前的卡片也需要移动。 If you left-align the cards however (using a spacer) the animation delay will be present for the cards that do not need to shift (the animation starts with an awkward delay).但是,如果您将卡左对齐(使用间隔),则不需要移动的卡会出现 animation 延迟(animation 以尴尬的延迟开始)。 If you need to account for this case, you'll need to make the newly inserted index part of the model.如果您需要考虑这种情况,您需要使新插入的索引部分成为 model 的一部分。

(This may be duplicating what Jack Goossen has written; go look there first, and if it's not clear, this may give some more explanation.) (这可能是在重复 Jack Goossen 所写的内容;go 先看那里,如果不清楚,这可能会给出更多解释。)

The core problem here is that you appear to be treating a struct as a reference type.这里的核心问题是您似乎将结构视为引用类型。 A struct is a value.结构是一个值。 That means that each holder of it has its own copy.这意味着它的每个持有者都有自己的副本。 If you pass a value to a Timer, then the Timer is mutating its own copy of that value, which can't be self .如果您将值传递给 Timer,则 Timer 正在改变它自己的该值的副本,它不能是self

In SwiftUI, models are typically reference types (classes).在 SwiftUI 中,模型通常是引用类型(类)。 They represent an identifiable "thing" that can be observed and changes over time.它们代表了一种可识别的“事物”,可以观察到并随着时间的推移而变化。 Changing this type to a class would likely address your problem.将此类型更改为 class 可能会解决您的问题。 (See Jack Goossen's answer, which uses a class to hold the cards.) (请参阅 Jack Goossen 的回答,它使用 class 来保存卡片。)

This is backwards of the direction that Swift had been moving in with UIKit, where views were reference types and the model was encouraged to be made of value types.这与 Swift 与 UIKit 的方向相反,其中视图是引用类型,并且鼓励 model 由值类型组成。 In SwiftUI, views are structs, and the model is usually made of classes.在 SwiftUI 中,视图是结构,而 model 通常由类组成。

(Using Combine with SwiftUI, it's possible to make both view and model into value types, but that's possibly beyond what you were trying to do here, and is a bit more complex if you haven't studied Combine or reactive programming already.) (使用与 SwiftUI 结合使用,可以将视图和 model 都变成值类型,但这可能超出了您在这里尝试做的事情,如果您还没有研究过组合或反应式编程,那就有点复杂了。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM