I have a card game, where user guesses two pairs of similar cards by flipping them on click. The issue is when user selects CardType or CardColor in editViewController, there is no any changes in BoardGameController.
My code is based on MVK: Model: Card.swift
import UIKit
// types of figure
enum CardType: CaseIterable {
case circle
case transparent
case cross
case square
case fill
}
// Color of card
enum CardColor: CaseIterable {
case red
case green
case black
case gray
case brown
case yellow
case purple
case orange
}
// Game card
typealias Card = (type: CardType, color: CardColor)
Model: Game.swift
import Foundation
class Game {
// number of pairs of unique cards
var cardsCount = 0
// array of generated cards
var cards = [Card]()// Array of Card objects instead of [(type: CardType, color: CardColor)]
// generation array of random cards
func generateCards() {
// generating new array of cards
var cards = [Card]()
for _ in 0...cardsCount {
let randomElement = (type: CardType.allCases.randomElement()!, color: CardColor.allCases.randomElement()!)
cards.append(randomElement)
}
self.cards = cards
// Add selected card type and color to the cards array
}
// equivalence check of cards
func checkCards(_ firstCard: Card, _ secondCard: Card) -> Bool {
if firstCard == secondCard {
return true
}
return false
}
}
Controllers: BoardGameViewController
import UIKit
class BoardGameController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// adding Start button to the scene
view.addSubview(startButtonView)
// adding game field on tthe scene
view.addSubview(boardGameView)
view.addSubview(flippButtonView)
view.backgroundColor = .white
// adding Edit button
view.addSubview(editButtonView)
}
// number pair of unique cards
var cardsPairCounts = 8
// essence "Game"
lazy var game: Game = getNewGame()
private func getNewGame() -> Game {
let game = Game()
game.cardsCount = self.cardsPairCounts - 1
game.generateCards()
return game
}
private var flippedCards = [UIView]()
// Button for lounch and update the gaame
// lazy var startButtonView = getStartButtonView()
lazy var startButtonView: UIButton = {
let button = getStartButtonView()
button.addTarget(self, action: #selector(startGame(_:)), for: .touchUpInside)
return button
}()
func getStartButtonView() -> UIButton {
// 1
// creating button
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
// 2
// changing position of button
button.center.x = view.center.x
//getting access to the current window
let window = UIApplication.shared.windows[0]
// determine indent of upp from border of window to the Save Area
let topPadding = window.safeAreaInsets.top
// setting coordinates Y of button according to indent
button.frame.origin.y = topPadding
//3
// setting up appearance of button
// set text
button.setTitle("Start the game", for: .normal)
// setting color for usual(untouched) state of button
button.setTitleColor(.black, for: .normal)
// setting color for touched sttate of button
button.setTitleColor(.gray, for: .highlighted)
// setting background color
button.backgroundColor = .systemGray4
// round off corners
button.layer.cornerRadius = 10
// Conecting the handler of press putton
button.addTarget(nil, action: #selector(startGame(_:)), for: .touchUpInside)
return button
}
// connection to the button touch
@objc func startGame(_ sender: UIButton) {
game = getNewGame()
let cards = getCardsBy(modelData: game.cards)
placeCardsOnBoard(cards)
}
// Buttton of Flipping all cards
// Button for lounch and flipp the all cards
lazy var flippButtonView = getFlippButtonView()
func getFlippButtonView() -> UIButton {
// 1
// creating button
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
// 2
// changing position of button
button.center.x = view.center.x
//getting access to the current window
let window = UIApplication.shared.windows[0]
// determine indent of upp from border of window to the Save Area
let bottomPadding = window.safeAreaInsets.bottom
// setting coordinates Y of button according to indent
//button.frame.origin.y = boardGameView.frame.origin.y + boardGameView.frame.size.height + 10
button.frame.origin.y = UIScreen.main.bounds.height - button.frame.height - bottomPadding
//3
// setting up appearance of button
// set text
button.setTitle("Flipp cards", for: .normal)
// setting color for usual(untouched) state of button
button.setTitleColor(.black, for: .normal)
// setting color for touched sttate of button
button.setTitleColor(.gray, for: .highlighted)
// setting background color
button.backgroundColor = .systemGray4
// round off corners
button.layer.cornerRadius = 10
// Conecting the handler of press putton
button.addTarget(nil, action: #selector(flippAllCards(_:)), for: .touchUpInside)
return button
}
// connection to the flipp button touch
@objc func flippAllCards(_ sender: UIButton) {
let shouldFlipAllCards = flippedCards.isEmpty || flippedCards.count == cardViews.count
for card in cardViews {
let cardView = card as! FlippableView
if shouldFlipAllCards {
cardView.flip()
} else if cardView.isFlipped {
cardView.flip()
}
}
flippedCards = shouldFlipAllCards ? cardViews : []
}
// Setting Button
lazy var editButtonView = getEditButtonView()
func getEditButtonView() -> UIButton {
// 1
// creating button
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
// 4. Position the button using frame-based approach
let window = UIApplication.shared.windows[0]
let rightPadding = window.safeAreaInsets.right
let buttonX = view.frame.width - rightPadding - button.frame.width - 16
let anotherElementY = boardGameView.frame.origin.y // Adjust this line based on the desired element
let buttonY = anotherElementY - button.frame.height - 10 // Place the button 10 points above the other element
button.frame.origin = CGPoint(x: buttonX, y: buttonY)
//3
// setting up appearance of button
// set text
button.setTitle("Edit", for: .normal)
// setting color for usual(untouched) state of button
button.setTitleColor(UIColor(red: 0.30, green: 0.50, blue: 0.40, alpha: 0.3), for: .normal)
// setting color for touched sttate of button
button.setTitleColor(UIColor(red: 0.1, green: 0.9, blue: 0.1, alpha: 0.3), for: .highlighted)
// setting background color
button.backgroundColor = .systemGray4
// round off corners
button.layer.cornerRadius = 10
// Conecting the handler of press putton
button.addTarget(self, action: #selector(editGame(_:)), for: .touchUpInside)
return button
}
// var game1: Game?
@objc func editGame(_ sender: UIButton) {
let targetViewController = EditViewController()
targetViewController.modalPresentationStyle = .fullScreen
targetViewController.numberOfPairsCallback = { [weak self] numberOfPairs in
self?.cardsPairCounts = numberOfPairs
}
present(targetViewController, animated: true, completion: nil)
}
//Game field
lazy var boardGameView = getBoardGameView()
private func getBoardGameView() -> UIView {
// indent of game field from closest item
let margin: CGFloat = 10
let boardView = UIView()
// indicating coordinates
// x
boardView.frame.origin.x = margin
// y
let window = UIApplication.shared.windows[0]
let topPadding = window.safeAreaInsets.top
boardView.frame.origin.y = topPadding + startButtonView.frame.height + margin
// calculating width
boardView.frame.size.width = UIScreen.main.bounds.width - margin*2
// calculating height
// with lowest indent
let bottomPadding = window.safeAreaInsets.bottom
//boardView.frame.size.height = UIScreen.main.bounds.height - boardView.frame.origin.y - margin - bottomPadding
boardView.frame.size.height = UIScreen.main.bounds.height - boardView.frame.origin.y - margin - bottomPadding - 50
// change style of game field
boardView.layer.cornerRadius = 5
boardView.backgroundColor = UIColor(red: 0.1, green: 0.9, blue: 0.1, alpha: 0.3)
// Add boardView to the view hierarchy
view.addSubview(boardView)
return boardView
}
// pressing button to get arrays of views based on Model
private func getCardsBy(modelData: [Card]) -> [UIView] {
// storage for views of cards
var cardViews = [UIView]()
// factory of cards
let cardViewFactory = CardViewFactory()
// sort out array of cards in the Model
for (index, modelCard) in modelData.enumerated() {
// adding first instance of cards
let cardOne = cardViewFactory.get(modelCard.type, withSize: cardSize, andColor: modelCard.color)
cardOne.tag = index
cardViews.append(cardOne)
// adding second instance of cards
let cardTwo = cardViewFactory.get(modelCard.type, withSize: cardSize, andColor: modelCard.color)
cardTwo.tag = index
cardViews.append(cardTwo)
}
for card in cardViews {
(card as! FlippableView).flipCompletionHandler = { [self] flippedCard in
// transfer a card to upp of hierarchy
flippedCard.superview?.bringSubviewToFront(flippedCard)
// add or delete cards
if flippedCard.isFlipped {
self.flippedCards.append(flippedCard)
} else {
if let cardIndex = self.flippedCards.firstIndex(of: flippedCard) {
self.flippedCards.remove(at: cardIndex)
}
}
// if flippet 2 cards
if self.flippedCards.count == 2 {
// get cards from data Model
let firstCard = game.cards[self.flippedCards.first!.tag]
let secondCard = game.cards[self.flippedCards.last!.tag]
// if cards are identical
if game.checkCards(firstCard, secondCard) {
// at first animationaly hidding them
UIView.animate(withDuration: 0.3, animations: {
self.flippedCards.first!.layer.opacity = 0
self.flippedCards.last!.layer.opacity = 0
// after that deleting them
}, completion: {_ in
self.flippedCards.first!.removeFromSuperview()
self.flippedCards.last!.removeFromSuperview()
self.flippedCards = []
})
// Otherwise
} else {
// flipping cards shirt upp
for card in self.flippedCards {
(card as! FlippableView).flip()
}
}
}
}
}
// adding all
return cardViews
}
// size and positions of cards
private var cardSize: CGSize {
CGSize(width: 80, height: 120)
}
// limit coordinaates of placement cards
private var cardMaxXCoordinate: Int {
Int(boardGameView.frame.width - cardSize.width)
}
private var cardMaxYCoordinate: Int {
Int(boardGameView.frame.height - cardSize.height)
}
var cardViews = [UIView]()
private func placeCardsOnBoard(_ cards: [UIView]) {
// deleting all aviable cardson the field
cardViews.forEach{ $0.removeFromSuperview() }
cardViews = cards
// sort out cards
for card in cardViews {
// for each cards generating random coordinates
let randomXCoordinate = Int.random(in: 0...cardMaxXCoordinate)
let randomYCoordinate = Int.random(in: 0...cardMaxYCoordinate)
card.frame.origin = CGPoint(x: randomXCoordinate, y: randomYCoordinate)
// placing card on the field
boardGameView.addSubview(card)
}
}
}
Controller: EditViewController
import UIKit
class EditViewController: UIViewController {
var game: Game? // Add a reference to the Game object
var numberOfPairsCallback: ((Int) -> Void)? // Блок обратного вызова
lazy var backButtonView = getBackButtonView()
func getBackButtonView() -> UIButton {
// create "Back" button with symbol Image
let backButton = UIButton(type: .system)
backButton.setImage(UIImage(systemName: "chevron.left"), for: .normal)
backButton.setTitle("Back", for: .normal)
backButton.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
view.addSubview(backButton)
//settin constraintsfor button
backButton.translatesAutoresizingMaskIntoConstraints = false
backButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
backButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive = true
return backButton
}
// connection to the button touch
@objc func backButtonTapped() {
// click handling
// closing current Scene
dismiss(animated: true, completion: nil)
}
// Metod dlya sohraneniya zadachi
// Button for lounch and update the gaame
lazy var saveButtonView = saveButton()
func saveButton() -> UIButton {
let saveButton = UIButton(type: .system)
saveButton.setTitle("Save", for: .normal)
saveButton.addTarget(self, action: #selector(saveButtonTapped), for: .touchUpInside)
saveButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(saveButton)
saveButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10).isActive = true
saveButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true
return saveButton
}
// connection to the button touch
@objc func saveButtonTapped() {
dismiss(animated: true, completion: nil)
}
// Button for lounch and update the gaame
let labelOfSelectPairCard: UILabel? = nil
func selectPairCards() -> UILabel {
let selectPair = UILabel()
selectPair.translatesAutoresizingMaskIntoConstraints = false
selectPair.text = "Select pair of cards"
self.view.addSubview(selectPair)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(selectCardPairsButtonTapped(_:)))
selectPair.isUserInteractionEnabled = true
selectPair.addGestureRecognizer(tapGesture)
NSLayoutConstraint.activate([
selectPair.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 20),
selectPair.topAnchor.constraint(equalTo: backButtonView.topAnchor, constant: 40)
])
return selectPair
}
// Selection pair of alike cards funcs of first label
@objc func selectCardPairsButtonTapped(_ gestureRecognizer: UITapGestureRecognizer ) {
let alertController = UIAlertController(title: "Select Card Pairs", message: nil, preferredStyle: .alert)
alertController.addTextField { textField in
textField.placeholder = "Enter the number of pairs"
textField.keyboardType = .numberPad
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self, weak alertController] _ in
guard let textField = alertController?.textFields?.first,
let text = textField.text,
let numberOfPairs = Int(text) else {
return
}
self?.game?.cardsCount = numberOfPairs
self?.numberOfPairsCallback?(numberOfPairs)
// self?.updateNumberOfPairsLabel()
}
alertController.addAction(cancelAction)
alertController.addAction(okAction)
present(alertController, animated: true, completion: nil)
}
// selection shapes of cards
var typeLabel: UILabel?
var colorLabel: UILabel?
func selectType() -> UILabel {
let selectType = UILabel()
selectType.translatesAutoresizingMaskIntoConstraints = false
selectType.text = "Select type of cards"
self.view.addSubview(selectType)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(lebelOfSelectTypeTapped(_:)))
selectType.isUserInteractionEnabled = true
selectType.addGestureRecognizer(tapGesture)
NSLayoutConstraint.activate([
selectType.leadingAnchor.constraint(equalTo: selectPairCards().leadingAnchor),
selectType.topAnchor.constraint(equalTo: selectPairCards().bottomAnchor, constant: 20)
])
return selectType
}
// Selection of card types
func selectTypeView() {
let alertController = UIAlertController(title: "Select Card Type", message: nil, preferredStyle: .actionSheet)
// Add action for each card type
for type in CardType.allCases {
let action = UIAlertAction(title: getTitle(for: type), style: .default) { [weak self] _ in
// Handle selection
// Handle selection
self?.handleCardTypeSelection(type)
// Update the game object's card type
}
alertController.addAction(action)
}
// Add cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
// Present the alert controller
present(alertController, animated: true, completion: nil)
}
// Get the title for the card type based on its enum case
private func getTitle(for type: CardType) -> String {
switch type {
case .circle:
return "Circle"
case .transparent:
return "Transparent"
case .cross:
return "Cross"
case .square:
return "Square"
case .fill:
return "Fill"
}
}
// Lebel of Select Color
func selectColor() -> UILabel {
let selectColor = UILabel()
colorLabel = selectColor // Store the label in the property
//label4.frame = CGRect(x: 20, y: 250, width: 300, height: 30)
selectColor.translatesAutoresizingMaskIntoConstraints = false
selectColor.text = "Select color"
self.view.addSubview(selectColor)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(selectCardColorButtonTapped))
selectColor.isUserInteractionEnabled = true
selectColor.addGestureRecognizer(tapGesture)
NSLayoutConstraint.activate([
selectColor.leadingAnchor.constraint(equalTo: selectPairCards().leadingAnchor),
selectColor.topAnchor.constraint(equalTo: selectType().bottomAnchor, constant: 20)
])
return selectColor
}
// Selection of card colors
func selectColorView() {
let alertController = UIAlertController(title: "Select Card Color", message: nil, preferredStyle: .actionSheet)
// Add action for each color
for color in CardColor.allCases {
let action = UIAlertAction(title: getTitle(for: color), style: .default) { [weak self] _ in
// Handle selection
self?.handleCardColorSelection(color)
}
alertController.addAction(action)
}
// Add cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
// Present the alert controller
present(alertController, animated: true, completion: nil)
}
// Get the title for the card color based on its enum case
private func getTitle(for color: CardColor) -> String {
switch color {
case .red:
return "Red"
case .green:
return "Green"
case .black:
return "Black"
case .gray:
return "Gray"
case .brown:
return "Brown"
case .yellow:
return "Yellow"
case .purple:
return "Purple"
case .orange:
return "Orange"
}
}
// Handle card type selection
func handleCardTypeSelection(_ type: CardType) {
// Update the card type in the game object
}
// Handle card color selection
func handleCardColorSelection(_ color: CardColor) {
}
@objc func lebelOfSelectTypeTapped(_ gestureRecognizer: UITapGestureRecognizer) {
selectTypeView()
}
// Connection to the button touch
@objc func selectCardColorButtonTapped() {
selectColorView()
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(backButtonView)
view.addSubview(saveButtonView)
typeLabel = selectType() // Store the label returned by selectType()
colorLabel = selectColor() // Store the label returned by selectColor()
}
}
Helpers: CardViewFactory
import UIKit
class CardViewFactory {
func get(_ shape: CardType, withSize size: CGSize, andColor color: CardColor) -> UIView {
// на основе размеров определяем фрейм
let frame = CGRect(origin: .zero, size: size)
// опредяем UI-цвет на основе цвета модели
let viewColor = getViewColorBy(modelColor: color)
// генерируем и возвращаем карточку
switch shape {
case .circle:
return CardView<CircleShape>(frame: frame, color: viewColor)
case .cross:
return CardView<CrossShape>(frame: frame, color: viewColor)
case .square:
return CardView<SquareShape>(frame: frame, color: viewColor)
case .fill:
return CardView<fillShape>(frame: frame, color: viewColor)
case .transparent:
return CardView<TransparentCircle>(frame: frame, color: viewColor)
}
}
// преобразует цвет Модели в цвет Представления
private func getViewColorBy(modelColor: CardColor) -> UIColor {
switch modelColor {
case .black:
return .black
case .red:
return .red
case .green:
return .green
case .gray:
return .gray
case .brown:
return .brown
case .yellow:
return .yellow
case .purple:
return .purple
case .orange:
return .orange
}
}
}