如何在此特定用例中正确隔离我的视图和模型

时间:2018-01-10 01:03:06

标签: ios swift model-view-controller

所以我正在通过MOOC,斯坦福大学的“使用Swift开发iOS 11应用程序”,在课程中,教授高度强调在MVC架构中将View与模型分开是多么重要。

其中一项任务是此游戏的简单实现:Set。虽然我确实将它们分离了,但感觉关闭

我正在寻找有关如何更多地改进模型结构的建议,而不添加特定的视图逻辑。例如,当我开始时,我没有使用Ints作为我的卡模型中的字段,而是使用了这样的枚举:

enum Symbol {
  case square, circle, triangle
}

哪种方法有效,但教授建议不要这样做,因为我们会更新View层以使用不同的符号。这是我最终使用的代码,但正如我上面提到的那样,感觉很好,并且想知道更有经验的人会如何处理这个问题(最相关的部分是Card.swift,但我包含了所有内容以说明它是如何被使用的):< / p>

Card.swift

import Foundation

struct Card : Hashable {
    var hashValue: Int {
        return self.identifier
    }

    static func ==(lhs: Card, rhs: Card) -> Bool {
        return lhs.identifier == rhs.identifier
    }

    let symbol: Int
    let color: Int
    let shading: Int
    let quantity: Int

    private let identifier: Int
    private static var previousIdentifier = 0
    private static func generateUid() -> Int {
        previousIdentifier += 1
        return previousIdentifier
    }

    init(symbol: Int, color: Int, shading: Int, quantity: Int) {
        self.symbol = symbol
        self.color = color
        self.shading = shading
        self.quantity = quantity
        self.identifier = Card.generateUid()
    }

    static func doesMakeSet(_ cards: [Card]) -> Bool {
        let colors = cards[0].color == cards[1].color && cards[1].color == cards[2].color
        let shading = cards[0].shading == cards[1].shading && cards[1].shading == cards[2].shading
        let quantities = cards[0].quantity == cards[1].quantity && cards[1].quantity == cards[2].quantity
        let symbols = cards[0].symbol == cards[1].symbol && cards[1].symbol == cards[2].symbol
        return colors || shading || quantities || symbols
    }
}

Set.swift

import Foundation

class Set {
    private var deck = [Card]()
    private(set) var board = [Card]() {
        didSet {
            assert(board.count <= 24, "Set.board: board cannot contain more than 24 cards")
        }
    }
    var canDealMoreCards: Bool {
        let matchesThatCanBeRemoved = board.filter { matchedCards.contains($0) }
        let occupiedSpacesOnBoard = board.count - matchesThatCanBeRemoved.count
        let freeSpaces = 24 - occupiedSpacesOnBoard
        let cardsRemaining = deck.count
        return cardsRemaining != 0 && freeSpaces >= 3
    }

    private(set) var selectedCards = [Card]()
    private(set) var matchedCards = [Card]()
    var currentSelectionIsMatch: Bool?

    private(set) var score = 0

    private func createDeck() -> [Card] {
        var newDeck = [Card]()
        let noOfOptions = 1...3
        for quantity in noOfOptions {
            for symbol in noOfOptions {
                for color in noOfOptions {
                    for shading in noOfOptions {
                        newDeck.append(Card(symbol: symbol, color: color, shading: shading, quantity: quantity))
                    }
                }
            }
        }
        return newDeck
    }

    func dealCards(numberOfCards: Int = 3) {
        if numberOfCards + board.count > 24 {
            board = board.filter { !matchedCards.contains($0) }
        }
        if deck.count >= numberOfCards {
            let range = 0..<numberOfCards
            board.append(contentsOf: deck[range])
            deck.removeSubrange(range)
            assert(board.count <= 24, "Set.dealCards(): there can't be more than 24 cards on the board but \(board.count) are")
        }
    }

    func cardChosen(index: Int) {
        if index > board.count - 1 {
            return
        }
        let card = board[index]
        if matchedCards.contains(card) {
            return
        }
        if selectedCards.count == 3 {
            if Card.doesMakeSet(selectedCards) {
                matchedCards += selectedCards
                selectedCards.removeAll()
                score += 3
                currentSelectionIsMatch = true
                dealCards()
            } else {
                currentSelectionIsMatch = false
                selectedCards.removeAll()
                score -= 5
            }
        } else {
            currentSelectionIsMatch = nil
        }
        if selectedCards.contains(card) {
            selectedCards.remove(at: selectedCards.index(of: card)!)
        } else {
            if !matchedCards.contains(card) {
                selectedCards.append(card)
            }
        }
    }

    init() {
        deck = createDeck()
        var shuffled = [Card]()
        for _ in deck.indices {
            let randomIndex = deck.count.arc4random
            shuffled.append(deck.remove(at: randomIndex))
        }
        deck = shuffled
        assert(deck.count == 81)
        dealCards(numberOfCards: 12)
    }
}

extension Int {
    var arc4random: Int {
        if self > 0 {
            return Int(arc4random_uniform(UInt32(self)))
        } else if self < 0 {
            return -Int(arc4random_uniform(UInt32(abs(self))))
        } else {
            return 0
        }
    }
}

ViewController.swift

import UIKit

class ViewController: UIViewController {

    var game = Set()

    @IBOutlet var cardButtons: [UIButton]!
    @IBOutlet weak var deal3CardsButton: UIButton!
    @IBOutlet weak var scoreLabel: UILabel!
    @IBOutlet weak var statusLabel: UILabel!

    override func viewDidLoad() {
        updateViewFromModel()
    }

    @IBAction func cardTouched(_ sender: UIButton) {
        if let index = cardButtons.index(of: sender) {
            game.cardChosen(index: index)
            updateViewFromModel()
        }

    }

    @IBAction func deal3CardsTouched() {
        game.dealCards()
        updateViewFromModel()
    }

    @IBAction func newGameTouched() {
        game = Set()
        updateViewFromModel()
    }

    func updateViewFromModel() {
        scoreLabel.text = "Score: \(game.score)"
        if let match = game.currentSelectionIsMatch {
            statusLabel.text = match ? "Match!" : "Not a Match!"
        } else {
            statusLabel.text = ""
        }
        cardButtons.forEach {
            $0.hide()
        }
        for index in game.board.indices {
            let text = getAttributedString(forCard: game.board[index])
            cardButtons[index].setAttributedTitle(text, for: UIControlState.normal)
            if game.selectedCards.contains(game.board[index]) {
                cardButtons[index].backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
            } else if game.matchedCards.contains(game.board[index]) {
                cardButtons[index].hide()
            } else {
                cardButtons[index].backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
            }
        }
        deal3CardsButton.isUserInteractionEnabled = game.canDealMoreCards
        deal3CardsButton.backgroundColor = game.canDealMoreCards ? #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) : #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)

    }

    let symbols = ["▲", "●", "■"]
    let colors = [#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1), #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1), #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1)]

    func getAttributedString (forCard card: Card) -> NSAttributedString {
        let symbol = symbols[card.symbol-1]
        let string = String(repeating: symbol, count: card.quantity)
        let color = colors[card.color-1]
        let shading = Shading(rawValue: card.shading)!
        var attributes: [NSAttributedStringKey: Any] = [
            .strokeWidth: -3,
            .strokeColor: color,
            .foregroundColor: color
        ]
        switch shading {
        case .fill:
            break
        case .open:
            attributes[.foregroundColor] = color.withAlphaComponent(0)
        case .striped:
            attributes[.foregroundColor] = color.withAlphaComponent(0.25)
        }

        return NSAttributedString(string: string, attributes: attributes)
    }

//    var circle = Symbol.circle

}

// ▲ ● ■

enum Shading: Int {
    case fill = 1, striped, open
}

extension UIButton {
    func hide() {
        self.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0)
        self.setTitle(nil, for: UIControlState.normal)
        self.setAttributedTitle(nil, for: UIControlState.normal)
    }
}

0 个答案:

没有答案