I am working my way through the Stanford Spring 2020 iOS course.
I got this error in my code: Use of unresolved identifier 'isMatched' (Actually Xcode displays this same error two times).
Please help me :( I can't see anything wrong in my code. Here's my code:
import Foundation
import SwiftUI
struct MemoryGame<CardContent> where CardContent: Equatable {
var cards: Array<Card>
var theme: Theme //Assignment 2
var score: Int = 0
var indexOfOneAndOnlyFaceUpCard: Int? {
get { cards.indices.filter { cards[$0].isFaceUp }.only }
set {
for index in cards.indices { // sets all other than current "indexOfOneAndOnlyFaceUpCard" cards to isFaceUp = false
cards[index].isFaceUp = index == newValue
}
}
}
mutating func choose(card: Card) {
if let chosenIndex = cards.firstIndex(matching: card), !cards[chosenIndex].isFaceUp, !cards[chosenIndex].isMatched { //if chosen card is not faceUp and not yet matched
if let potentialMatchIndex = indexOfOneAndOnlyFaceUpCard { // if one card is already face up
if cards[chosenIndex].content == cards[potentialMatchIndex].content { // if already faceUp card = chosen card
cards[chosenIndex].isMatched = true
cards[potentialMatchIndex].isMatched = true
score += 2
} else /* if both cards not the same */ {
//if card.isSeen {score -= 1} // and the currently choosen one isSeen - my bad solution
}
self.cards[chosenIndex].isFaceUp = true
} else { // if this (currently choosen card) is only faceUp card
indexOfOneAndOnlyFaceUpCard = chosenIndex
}
}
// Assignment 2 - probably good fragment
if card.isSeen == false {
if let chosenIndex = cards.firstIndex(matching: card) {
cards[chosenIndex].isSeen = true
}
}
}
init(numberOfPairsOfCards: Int, theme: Theme, cardContentFactory: (Int) -> CardContent) {// Assignment 2 theme: Theme
cards = Array<Card>()
for pairIndex in 0..<numberOfPairsOfCards {
let content = cardContentFactory(pairIndex)
cards.append(Card(content: content, id: pairIndex*2))
cards.append(Card(content: content, id: pairIndex*2+1))
}
cards.shuffle()
self.theme = theme
}
struct Card: Identifiable {
var isFaceUp: Bool = false
var isMatched: Bool = false
var isSeen: Bool = false
var content: CardContent
var id: Int
}
// Assignment 2:
struct Theme {
let name: String
let emojis: [CardContent]
let numberOfCardPairsToShow: Int?
let color: Color //import swiftui
init(name: String, emojis: [CardContent], numberOfCardPairsToShow: Int? = nil, color: Color) {
self.name = name
self.emojis = emojis
self.numberOfCardPairsToShow = numberOfCardPairsToShow
self.color = color
}
}
}
The places where you have cards[chosenIndex].isMatched = true and cards[potentialMatchIndex].isMatched = true could be the issue.
The array[index] returns an optional. This is because the index could be invalid.
You need to unwrap the value before you can access isMatched.
Like so :
if let chosenCard = cards[chosenIndex] as? Card {
chosenCard.isMatched = true
}
Related
I am adding in some functionalities for this iOS swift matching card game and I need to check if the first 2 buttons flipped over match. They have four types of emojis for eight cards, which means there are 4 pairs of matches. I am having trouble finding out how to check if the cards match and when they match, I need the background color of the buttons to opaque (invisible). Everything else works except the empty if statement in the concentration class within the chooseCard function. That is where I need help.
Here's all the code so you can see whats related to what:
class ViewController: UIViewController {
lazy var game = Concentration(numberOfPairsOfCards: (cardButtons.count + 1) / 2)
var flipCount = 0 {
// Property Observer
didSet { flipLabel.text = "Flips: \(flipCount)" }
}
#IBOutlet weak var flipLabel: UILabel!
#IBOutlet var cardButtons: [UIButton]!
#IBAction func touchCard(_ sender: UIButton) {
flipCount+=1
if let cardNumber = cardButtons.firstIndex(of: sender) {
game.chooseCard(at: cardNumber)
updateViewFromModel()
} else {
print ("chosen card was not in cardButtons")
}
}
var emojiChoices = ["👻", "🎃", "🙏🏾", "🦆"]
var emoji = [Int:String]()
func emoji(for card: Card) -> String {
if emoji[card.identifier] == nil, emojiChoices.count > 0 {
let randomIndex = Int(arc4random_uniform(UInt32(emojiChoices.count)))
emoji[card.identifier] = emojiChoices.remove(at: randomIndex)
}
return emoji[card.identifier] ?? "?"
}
func updateViewFromModel() {
for index in cardButtons.indices {
let button = cardButtons[index]
let card = game.cards[index]
if card.isFaceUp {
button.setTitle(emoji(for: card), for: UIControl.State.normal)
button.backgroundColor = UIColor.white
}
else {
button.setTitle("", for: UIControl.State.normal)
button.backgroundColor = UIColor.orange
}
}
}
}
import Foundation
class Concentration {
var cards = [Card]()
func chooseCard(at index: Int) {
if cards[index].isFaceUp {
cards[index].isFaceUp = false
}
else {
cards[index].isFaceUp = true
}
if cards[index].isFaceUp && cards[index].isMatched {
}
}
init(numberOfPairsOfCards: Int) {
for _ in 0..<numberOfPairsOfCards {
let card = Card()
cards += [card,card]
}
//TODO: Shuffle cards
cards.shuffle()
}
}
import Foundation
struct Card {
var isMatched = false
var isFaceUp = false
var identifier: Int
static var identifierFactory = 0
static func getUniqueIdentifier() -> Int {
Card.identifierFactory += 1
return Card.identifierFactory
}
init() {
self.identifier = Card.getUniqueIdentifier()
}
}
In your concentration class you will check for faceUp card indexes
Change your Concentration from class to Struct
struct Concentration { // instead of class Concentration
private var indexOfFaceUpCard: Int? {
get {
let faceUpCardIndices = cards.indices.filter { cards[$0].isFaceUp }
return faceUpCardIndices.count == 1 ? faceUpCardIndices.first : nil
} set {
for index in cards.indices {
cards[index].isFaceUp = (index == newValue)
}
}
}
Then you have mutating chooseCard method
mutating func chooseCard(at index: Int)
In your chooseCard method you check for matching
if !cards[index].isMatched {
if let matchIndex = indexOfFaceUpCard, matchIndex != index {
if cards[matchIndex] == cards[index] {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
}
cards[index].isFaceUp = true
} else {
indexOfFaceUpCard = index
}
}
So your method look like this
mutating func chooseCard(at index: Int) {
if !cards[index].isMatched {
if let matchIndex = indexOfFaceUpCard, matchIndex != index {
if cards[matchIndex] == cards[index] {
cards[matchIndex].isMatched = true
cards[index].isMatched = true
}
cards[index].isFaceUp = true
} else {
indexOfFaceUpCard = index
}
}
}
Update your card struct
struct Card: Hashable {
var isMatched = false
var isFaceUp = false
var identifier: Int
static var identifierFactory = 0
static func getUniqueIdentifier() -> Int {
Card.identifierFactory += 1
return Card.identifierFactory
}
static func ==(lhs: Card, rhs: Card) -> Bool {
return lhs.identifier == rhs.identifier
}
init() {
self.identifier = Card.getUniqueIdentifier()
}
}
I make a text player which subsequently displays the phrase in one language after some time, an English translation with the phrase playing out loud using mp3. There are buttons play, pause, next, previous.
I wrote the code, there are some bugs in it:
class Sentence: Codable {
var id: Int
var rus: String
var eng: String
var link: String
init(id: Int, rus: String, eng: String, link: String) {
self.id = id
self.rus = rus
self.eng = eng
self.link = link
}
}
class WordsViewController: UIViewController {
var sentences: [Sentence] = [Sentence]()
var isPlayTouch = false
var isIncrement = false
var speed: Double = 1.5
var position: Int = 0
#objc func playAction() {
self.isPlayTouch = true
if sentences.count > 0 {
let duration = self.getDurationAudio(link: self.sentences[self.position].link)
DispatchQueue.global().async {
if self.isPlayTouch {
Thread.sleep(forTimeInterval: duration * self.speed)
if self.isPlayTouch {
DispatchQueue.main.async {
self.playSound()
self.wordsView.sentence.text = self.sentences[self.position].eng
DispatchQueue.global().async {
if self.isPlayTouch {
Thread.sleep(forTimeInterval: duration)
DispatchQueue.main.async {
if self.isPlayTouch {
if self.position == self.sentences.count - 1 {
self.pauseAction()
if !self.isIncrement {
self.isIncrement = true
}
} else {
if self.isPlayTouch {
Thread.sleep(forTimeInterval: 0.5)
if self.isPlayTouch {
self.position = self.position + 1
self.setText()
if self.isPlayTouch {
self.playAction()
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
After pressing the pause button for the delay time, and immediately pressing the playlist, two phrases are played at once, this is due to checking on the isPlayTouch variable
Tell me how to write this functionality?
I have an array of objects and I want to compare the objects based on property to find out if the properties are all the same. Right now I loop through all the objects, place all values of the properties in a separate array, and then use filterArr.allSatisfy { $0 == filterArr.last } to detemermine wether the properties are all the same or not.
This method works but I know there has to be a more elegant way then what I'm doing.
I actually went looking for an answer to this but every single thing I came across was comparing the elements of 2 different arrays instead of 1.
class IceCream {
var flavor: String?
var price: Double?
}
let iceCream1 = IceCream()
iceCream1.flavor = "vanilla"
iceCream1.price = 1.5
let iceCream2 = IceCream()
iceCream2.flavor = "chocolate"
iceCream2.price = 2.0
let iceCream3 = IceCream()
iceCream3.flavor = "vanilla"
iceCream3.price = 1.5
let iceCream4 = IceCream()
iceCream4.flavor = "strawberry"
iceCream4.price = 2.5
let iceCreams = [iceCream1, iceCream2, iceCream3, iceCream4]
var filterArr = [String]()
for iceCream in iceCreams {
filterArr.append(iceCream.flavor ?? "")
}
let areItemsEqual = filterArr.allSatisfy { $0 == filterArr.last }
print(areItemsEqual) // prints false
You can avoid having to initialize and then assign the properties on separate lines with a struct.
struct IceCream {
let flavor: String?
let price: Double?
}
let iceCreams: [IceCream] = [
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "chocolate", price: 2.0),
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "strawberry", price: 2.5)
]
Using the generics sugar provided by #Alexander and #matt, we have a nice looking extension.
extension Collection {
func allEqual<T: Equatable>(by key: KeyPath<Element, T>) -> Bool {
return allSatisfy { first?[keyPath:key] == $0[keyPath:key] }
}
}
print(iceCreams.allEqual(by: \.flavor))
Alternatively, you could specify an IceCream be equal to one another by flavor.
extension IceCream: Equatable {
static func == (lhs: IceCream, rhs: IceCream) -> Bool {
return lhs.flavor == rhs.flavor
}
}
extension Collection where Element: Equatable {
func allEqual() -> Bool {
return allSatisfy { first == $0 }
}
}
print(iceCreams.allEqual())
Here's a pretty Swifty way to do it. I define an extension on Collection that checks for equality among the collection's items, according to a given predicate:
extension Collection {
func allEqual<T: Equatable>(by deriveKey: (Element) -> T) -> Bool {
guard let firstElement = self.first else {
return true // empty lists are all-equal
}
let sampleKey = deriveKey(firstElement)
return self.dropFirst().allSatisfy{ deriveKey($0) == sampleKey }
}
}
struct IceCream {
let flavor: String
let price: Double
}
let iceCreams = [
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "chocolate", price: 2.0),
IceCream(flavor: "vanilla", price: 1.5),
IceCream(flavor: "strawberry", price: 2.5)
]
let allItemsEqualByFlavour = iceCreams.allEqual(by: { $0.flavor})
print(allItemsEqualByFlavour) // false
let vanillaOnlyIceCreams = iceCreams.filter{ $0.flavor == "vanilla" }
print(vanillaOnlyIceCreams.allEqual(by: { $0.flavor})) // true
Here's an elegant way to make sure your ice creams are the same along any arbitrary axis, i.e. either flavor or price or any other equatable property you may later inject:
extension Array {
func allSameForProperty<T:Equatable> (_ p:KeyPath<Element,T>) -> Bool {
return self.isEmpty || self.allSatisfy{
self.first![keyPath:p] == $0[keyPath:p]
}
}
}
Let's test it. First, some initial conditions:
struct Icecream {
let flavor : String
let price : Double
}
let arr = [
Icecream(flavor: "vanilla", price: 1.5),
Icecream(flavor: "vanilla", price: 1.75)
]
And now the actual test:
arr.allSameForProperty(\Icecream.flavor) // true
arr.allSameForProperty(\Icecream.price) // false
Given the following example code:
class Loader {
var posts = [Post]()
init() {
for i in 131...141 {
posts.append(PostImpl(id: i))
}
}
}
protocol Post {
var id:Int {get}
var readByUser: Bool {get set}
}
class PostImpl: Post {
var id: Int
var readByUser: Bool = false;
init(id:Int) {
self.id = id
}
}
I want to mark the item with id == 135 as readByUser=true. Is there some way of doing this in a more compact/readable/easy way than:
let loader = Loader()
for (index,post) in loader.posts.enumerated() {
if post.id == 135 {
loader.posts[index].readByUser = true
}
}
It can be this if you want short:
loader.posts[loader.posts.index(where: { $0.id == 135 })!].readByUser = true
Or check optional if you want:
if let index = loader.posts.index(where: { $0.id == 135 }) {
loader.posts[index].readByUser = true
}
var loader = Loader()
loader.posts.filter { $0.id == 135 }.forEach { $0.readByUser = true }
This also looks good but because you have an array of items that implement a protocol. If you don't tell Swift that this is a class protocol, it will assume that it can be implemented by struct.
So one more change if you're going to use it.
protocol Post : class {
var id:Int { get }
var readByUser: Bool { get set }
}
I’ve an NSMutableArray (say accountListArray). The content of array is Model (say BankAccountModel with property defaultAcc - Y/N).
Now I want to sort the content of accountListArray based on default property i.e. all account that is defaultAcc enabled (Y) should be on top.
class BankAccountModel: NSObject {
static let sharedInstance: BankAccountModel = BankAccountModel()
var defaultAcc = "" // Y or N
}
How to do it?
Here is my example. It will sort array and after sort person with name 2 and person with name 4 will be in top of the array
class People{
var name = ""
var isMale:Bool = false
init(name:String,isMale:Bool){
self.name = name
self.isMale = isMale
}
}
var person1 = People(name: "1",isMale: false)
var person2 = People(name: "2",isMale: true)
var person3 = People(name: "3",isMale: false)
var person4 = People(name: "4",isMale: true)
var array = [person1,person2,person3,person4]
array.sort() { Int(NSNumber(value:($0.isMale))) > Int(NSNumber(value:($1.isMale))) }
Additional to the answers above. You can define your model as Comparable, then implement your own >, <, == protocol:
For example:
class BankAccountModel: Comparable { //or struct
var accountHolder: String = ""
var balance: Decimal?
var enabled: Bool = false
init(holder: String, balance: Decimal, enabled: Bool) {
self.accountHolder = holder
self.balance = balance
self.enabled = enabled
}
static func == (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return lhs.enabled == rhs.enabled
}
static func > (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return lhs.enabled || (!lhs.enabled && !rhs.enabled)
}
static func < (lhs: BankAccountModel, rhs: BankAccountModel) -> Bool {
return rhs.enabled || (!rhs.enabled && !lhs.enabled)
}
}
let account1 = BankAccountModel(holder: "Holder 1", balance: Decimal(10.0), enabled: true)
let account2 = BankAccountModel(holder: "Holder 2", balance: Decimal(20.3), enabled: false)
let account3 = BankAccountModel(holder: "Holder 3", balance: Decimal(-1.0), enabled: false)
let account4 = BankAccountModel(holder: "Holder 4", balance: Decimal(0.0), enabled: true)
var allAccounts = [account1, account2, account3, account4]
allAccounts.sort(by: {return $0 > $1})
Try this :
Create Model property as below
class BankAccountModel: NSObject {
static let sharedInstance: BankAccountModel = BankAccountModel()
var isDefault: Bool = false
// assign value
if let objDefault = dictionary["default"] {
isDefault = (objDefault as! String) == "Y" ? true : false
}
// var default = "" // Y or N
}
Sort as below
self.arrOfModels.sort {
return $0.isDefault && !$1.isDefault // isSelected is your flag . you need to change with your flag
}
OR
Another Solution with string
self.arrModels.sort {
return ($0. defaultAcc == "Y") && ($1. defaultAcc == "N")
}
Swift 3.0
Hope will work for you.
let sortedArr = accountListArray.sorted(by: {
let item1 = $0 as! BankAccountModel
let item2 = $1 as! BankAccountModel
if item1.defaultAcc > item2.defaultAcc {
return true
}
return false
})