Match game glitch

As I get fewer cards, they sometimes change position (shift to the right which occasionally means changing rows), which is weird since we didn’t delete the cars, just made it invisible. I got rid of most of this by eliminating the timer label width (which got rid of the warning that images may be clipped). Any ideas ? (It only happens on my iPhone X not on the simulator )

2 Likes

Check your constraints for the cell

Could you post your code for that view controller?

//
// CardCollectionViewCell.swift
// The Match app
//
// Created by Rick Blumenthal on 11/13/19.
// Copyright © 2019 Richard Blumenthal. All rights reserved.
//

import UIKit

class CardCollectionViewCell: UICollectionViewCell {

@IBOutlet weak var frontImageView: UIImageView!

@IBOutlet weak var backImageView: UIImageView!

var card:Card?

func setCard(_ card:Card){
    
    //Keep track of the card that gets passed
    self.card = card
    
    if card.isMatched == true {
        //if the cardi amatched make imageView invisible
        
        backImageView.alpha = 0
        frontImageView.alpha = 0
        
        return
    }
    
    else {
        //if the card hasnt been matched make the imageView visible
        
        backImageView.alpha = 1
        frontImageView.alpha = 1
        
    }
    frontImageView.image = UIImage(named: card.imageName)
    
    //determine if  card is flipped up or down
    
    if card.isFlipped == true {
    //make sure front is on top
        UIView.transition(from: backImageView, to: frontImageView, duration: 0, options: [.transitionFlipFromLeft,.showHideTransitionViews], completion: nil)
    }
    //make sure back is on top
    else {
        UIView.transition(from: frontImageView, to: backImageView, duration: 0, options: [.transitionFlipFromRight,.showHideTransitionViews], completion: nil)
    }
} //end func setCard
func flip() {
    
    UIView.transition(from: backImageView, to: frontImageView, duration: 0.3, options: [.transitionFlipFromLeft, .showHideTransitionViews], completion: nil)
}
func flipBack(){
    
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) {
    
        UIView.transition(from: self.frontImageView, to: self.backImageView, duration: 0.3, options: [.transitionFlipFromRight, .showHideTransitionViews], completion: nil)
        
    }
     
    
}

func remove() {
    
    //Removes both imageviews from being visible
    
    backImageView.alpha = 0
    
    //Animate it no need to animate back image since it will be covered by the front image
    
    UIView.animate(withDuration: 0.3, delay: 0.5, options: .curveEaseOut, animations: {
        self.frontImageView.alpha = 0
    }, completion: nil)

    
}

} //UICollectionView End

Actually it does happen on the simulator

copy of the output in the bottom of the screen of the Xcode app

2

8

7

10

5

11

4

1

2019-11-23 22:50:35.669980-0800 The Match app[5611:333601] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x600003fdd340> F8BB1C28-BAE8-11D6-9C31-00039315CD46

2019-11-23 22:50:35.691502-0800 The Match app[5611:333839] [ddagg] AggregateDevice.mm:790 couldn’t get default input device, ID = 0, err = 0!

2019-11-23 22:50:38.876235-0800 The Match app[5611:333841] [aqsrv] AQServer.cpp:68:APIResult: Exception caught in AudioQueueInternalNotifyRunning - error -66671

2019-11-23 22:50:56.485905-0800 The Match app[5611:333840] [aqsrv] AQServer.cpp:68:APIResult: Exception caught in AudioQueueInternalNotifyRunning - error -66671

Message from debugger: Terminated due to signal 15

The CardCollectionViewCell.swift looks good. What about your ViewController.swift file?

If they switch places it’d have to be something with how the collection view is dequeue-ing them

//
// ViewController.swift
// The Match app
//
// Created by Rick Blumenthal on 11/13/19.
// Copyright © 2019 Richard Blumenthal. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

@IBOutlet weak var timerLabel: UILabel!


@IBOutlet var collectionView: UICollectionView!




var model = CardModel()
var cardArray = [Card]()

var firstFlippedCardIndex:IndexPath?

var timer: Timer?
var milliseconds:Float = 60*1000 // 10 seconds



override func viewDidLoad() {
    super.viewDidLoad()
    
    //Call the getCards method of the cardModel
    cardArray = model.getCards()
    
    collectionView.delegate = self
    collectionView.dataSource = self
  
    //Create timer
    timer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: #selector(timerElapsed), userInfo: nil, repeats: true)
    RunLoop.main.add(timer!, forMode: .common)
    
}

override func viewDidAppear(_ animated: Bool) {
    
    SoundManager.playSound(.shuffle)
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    //Dispose of any resources that can be created.
}

@objc func timerElapsed() {
    
    milliseconds -= 1
    
    // Convert to seconds
    let seconds = String(format: "%.2f", milliseconds/1000)
    
    //Set Label
    timerLabel.text = "Time Remaining \(seconds)"
    
    //when timer reaches 0
    if milliseconds <= 0 {
        timer?.invalidate()
        timerLabel.textColor = UIColor.red
        
        //Check if there are any cards unmatched
        checkGameEnded()
    }
}

// MARK: - UICollectionView Protocol Methods

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return cardArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
    //get a CardCollectionViewCell object
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CardCell", for: indexPath) as! CardCollectionViewCell
    
    //get the card that the collection view is trying to display
    let card = cardArray[indexPath.row]
    
    //set the card for the cell
    cell.setCard(card)
    
    return cell
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
    //Check if there's any time left
    
    if milliseconds <= 0 {
        return
    }
    
    let cell = collectionView.cellForItem(at: indexPath) as! CardCollectionViewCell
    
    let card = cardArray[indexPath.row]
    
    if card.isFlipped == false && card.isMatched == false {
        
        //flip the card
        cell.flip()
        
        //Play the flip sound
        SoundManager.playSound(.flip)
       
        
        
        //set the status of the card
        card.isFlipped = true
        
        //determineif its the first card flipped
        if firstFlippedCardIndex == nil {
            
            //this is the first card flipped
            firstFlippedCardIndex = indexPath
        }
        else {
            
            //this is the seccond card
            checkForMatches(indexPath)
            
            //TODO: perform matching logic
        }
    }
    
    
    
} //End the didSelelctItem method

//MARK Game select methods

func checkForMatches(_ secondFlippedCardIndex: IndexPath) {
    
    //Get the cells for the two cards revealed
    let cardOneCell = collectionView.cellForItem(at: firstFlippedCardIndex!) as? CardCollectionViewCell
    
    let cardTwoCell = collectionView.cellForItem(at: secondFlippedCardIndex) as? CardCollectionViewCell
    
    //Get the cards for the two cards revealed
    let cardOne = cardArray[firstFlippedCardIndex!.row]
    let cardTwo = cardArray[secondFlippedCardIndex.row]
    
    //compare the cards
    if cardOne.imageName == cardTwo.imageName {
        
        //It's a match
        
        //Play the is correct sound
        SoundManager.playSound(.match)
        
        //set the status of the cards
        cardOne.isMatched = true
        cardTwo.isMatched = true
        
        //Remove the cards from the grid
        cardOneCell?.remove()
        cardTwoCell?.remove()
        
        //check if there are any cards unmatched
        checkGameEnded()
        
    }
    else {
        
        //It's not a match
        
        //Play the no match sound
        SoundManager.playSound(.nomatch)
    
        //set the status of the cards
        cardOne.isFlipped = false
        cardTwo.isFlipped = false
        
        //flip the cards back
        cardOneCell?.flipBack()
        cardTwoCell?.flipBack()
        
        
        
    }
    //Tell the collectionView to reload the cell of the first card if it is nil
    
    if cardOneCell == nil {
        collectionView.reloadItems(at: [firstFlippedCardIndex!])
    }
    
    //reset the property that tracks the first card flipped
    firstFlippedCardIndex = nil
}

func checkGameEnded () {
    
    //Determine if there are any cards unmatched
    var isWon = true
    
    for card in cardArray {
        
        if card.isMatched == false {
            isWon = false
            break
        }
    }
    
    //Messaging variable
    
    var title = ""
    var message = ""
    
    //If not then user has won, stop the timer
    
    if isWon == true {
        
        if milliseconds > 0 {
            timer?.invalidate()
            
        }
        title = "Congratualtions!"
        message = "You've won"
        
        //Play win sound
        SoundManager.playSound(.win)
    }
    else {
    //If there are unmatched cards, check if there's any cards left
        if milliseconds > 0 {
            return
        }
    //Display messages
        title = "Game Over"
        message = "You've lost"
        
        //Play the flip sound
        SoundManager.playSound(.lose)
        
        
    }
    
    //Show won/lost messaging
    
    showAlert(title, message)
}

func showAlert(_ title:String, _ message:String){
    let alert = UIAlertController(title: title, message: message, preferredStyle: .alert )
    let alertAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
    
    alert.addAction(alertAction)
    
    present(alert, animated: true, completion: nil)
    
}

} //End ViewController class

I double checked the video with the dequeue and I think I have it exact–can you verify (code is above this for the ViewController.Swift). Also if I can get this to run I am seriously considering the purchased video classes as well
Thanks

you removed the cells from the grid though… try commenting this out

this function is to make the cards invisible when there is a match, but I’ll try when I get home

the invisible was setting the alpha to 0 instead of 1 in the cardviewcell

removing it will remove it entirely from your list not just hiding it

in CardCollectionViewCell.swift the function remove is defined as below ( to set the image alpha to zero, not to remove that actual card)

func remove() {

    //Removes both imageviews from being visible
    
    backImageView.alpha = 0
    
    //Animate it no need to animate back image since it will be covered by the front image
    
    UIView.animate(withDuration: 0.2, delay: 0.4, options: .curveEaseOut, animations: {
        self.frontImageView.alpha = 0
    }, completion: nil)

I also have this issue now and then. I tried doing this and it caused the cards to remain visible even when I matched them.

1 Like

the remove function is only designed to make them invisible, not to actually remove them. I hope to have an answer from them soon

1 Like

Hey Rick the ViewController.swift looks good. I’d have to see the entire project to really see what’s wrong.
If you could zip the entire project (swift files and all) I, or anyone else, can look at it to try to help you out! :slight_smile:

I’m inclined to think that the issue is somewhere in Storyboard with a setting on the CollectionView. I’ve looked through your code and checked with my version and it looks OK.