Search code examples
iosswiftpass-by-referencepass-by-value

Issue with pass by value vs pass by reference in Swift?


I'm new to Swift but not new to iOS dev. I'm having an issue that I believe is caused by pass by value.

Below code is in the VC and works fine when tapping each button, i.e. each button changes background to show if it's selected or not.

    button.isSelected = card.isChosen // button is selected when the card is chosen
    if button.isSelected {
       button.setBackgroundColor(color: UIColor.darkGray, forState: UIControlState.selected)
    }

I have an issue in the game logic which is a struct. Struct contains an array of type Card which is really just a deck of cards and maps back to the UIButtons. When a card(UIButton) in the VC is touched, it calls a method (chooseCard) in the Struct. Among other things, this action will toggle if the card is selected by setting the card.isSelected = !card.isSelected and maps back to the UI to indicate if the card is selected or not. All of this works fine so far.

When 3 cards have been selected, it calls some match logic, which is currently just stubbed out. If there was no match, each of the cards .isSelected is set to false so that in the UI those cards will no longer appear selected. This is what is not working and I can't figure out why. My hunch is that I'm doing something wrong in the way I'm dealing with pass by value since the logic object is a struct. After 3 cards are selected and they are not matched (they won't since it's only stubbed), each card.isSelected should be set back to false, but they are still showing selected in the UI. I set a breakpoint in the VC and it shows the card.isSelected is still set to true. VC has has a setGame property which has access to the array of cards. Here is the relevant code...any help is appreciated!

    struct SetGame {

    private(set) var cards = [Card]()
    mutating public func chooseCard(at index: Int) {

        //TODO: Assertion here for index being < cards.count
        print("chooseCard(at index: \(index)")
        cards[index].isChosen = !cards[index].isChosen // toggle isChosen when card is selected

        if !cards[index].isMatched && cards[index].isChosen {
            //print("Card not matched, so we're good to go...")
            for var card in cards {
                if card.isChosen {
                    matchingCards.append(card)
                    // see if we have enough cards to match
                    if matchingCards.count > 2 {
                        //TODO: Need to call match logic here and set each matched card to .isMatched = true if matched
                        if card.isMatched {
                          print("Yay, matched!")
                        } else {
                            print("Card is not matched, flipping back over")
                            /*** THIS LINE NOT REFLECTING IN THE UI! ***/
                            card.isChosen = !card.isChosen // flip the unmatched card back over
                        }
                    }
                }
            }
            matchingCards.removeAll() // clear out all the cards from the matching
        } else {
            print("Card is either matched or being deselected...")
        }
    }

Solution

  • Your problem is that Card is a struct, so this line:

    for var card in cards {
    

    creates a copy of each card in cards, so setting any properties on that copy will not modify the card in your cards array.

    To fix this, loop over the indices of the array and refer to the cards as cards[idx]:

    struct SetGame {
    
    private(set) var cards = [Card]()
    mutating public func chooseCard(at index: Int) {
    
        //TODO: Assertion here for index being < cards.count
        print("chooseCard(at index: \(index)")
        cards[index].isChosen = !cards[index].isChosen // toggle isChosen when card is selected
    
        if !cards[index].isMatched && cards[index].isChosen {
            //print("Card not matched, so we're good to go...")
            for idx in cards.indices {
                if cards[idx].isChosen {
                    matchingCards.append(cards[idx])
                    // see if we have enough cards to match
                    if matchingCards.count > 2 {
                        //TODO: Need to call match logic here and set each matched card to .isMatched = true if matched
                        if cards[idx].isMatched {
                          print("Yay, matched!")
                        } else {
                            print("Card is not matched, flipping back over")
                            /*** THIS LINE NOT REFLECTING IN THE UI! ***/
                            cards[idx].isChosen = !cards[idx].isChosen // flip the unmatched card back over
                        }
                    }
                }
            }
            matchingCards.removeAll() // clear out all the cards from the matching
        } else {
            print("Card is either matched or being deselected...")
        }
    }
    

    or consider making Card a class so that when you are referring to a Card you know you are referring to the same object.