Way to query whether an object is a member of an array? - ios

I'm designing a FreeCell card game and I have both my cards (model) and cardViews (view) in arrays that represent columns of cards – and then the whole board is an array of columns:
//in the model:
typealias Card = DeckBuilder.Card
typealias Column = [Card]
var board = [Column](repeating: [], count: 8)
//in the view controller:
var boardForView = [[PlayingCardView]](repeatElement([PlayingCardView](), count: 8))
When a user taps on a cardView, I want the controller to take the selected cardView's column & row (where the column is which column in the board and the row is which card in the column) and then find the card in the corresponding position in the model.
I've got my tap gesture recognizer set up, and have extracted the cardView from it:
func selectCard(_ sender: UITapGestureRecognizer) {
if let selectedCard = sender.view as? PlayingCardView {
print("card selected: \(selectedCard.cardDescription?.string)")
}
}
So the question is: is there a property of the cardView that is its place in a column array, and then a property of that column that is the column's place in the boardForView array?

I figured it out! One way I had thought of was iterating through each card in each column of the board and checking to see if it == my current card. Seemed way too clunky. But then I found array.index(of: element) and my day is saved! I do have to iterate through each column on the board, but it's still much more elegant:
func selectCard(_ sender: UITapGestureRecognizer) {
if let currentCard = sender.view as? PlayingCardView {
for column in 1...boardForView.count {
if let row = boardForView[column - 1].index(of: currentCard) {
print("card selected: \(currentCard.cardDescription!.string) in row \(row+1) of column \(column)")
selectedCard = freeCellGame.board[column - 1][row]
print("card selected from model: \(selectedCard!.description)")
}
}
}
}

Since it's a board game, you surely have a logic set for how the board should be laid out. Make that logic work both ways. That is, use it to provide layout info while creating your board and also use it to get information about your board.
This way, when the gesture recognizer triggers, fetch the tap point and let your layout logic tell you the row and column of the card the tap came from.
Now, you can directly fetch the Card from your board property with those two indices (row and column).

Related

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...")
}
}
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.

How can I make an if statement to determine what array to use in a tableView?

Using xcode 9.4, Swift 4 and ios 11, I want to select an item from a collection view screen that goes to a table view screen. On the table view screen, I have a series of arrays and, depending on what I've chosen on the collection view screen, I want to display a specific array on the table view screen.
I've already set up the prepareForSegue function on the collection view screen that references a var on the table view, but I'm a bit stuck on the "select an array" bit. I'm pretty sure it involves an if statement, but I'm not sure how or where to put this if statement.
Any help would be greatly appreciated.
Here's the if statement that I made (I don't know if its correct):
if cellItem == "jeans" {
tableArray.append(jeanArray[])
} else if cellItem == "skirts" {
tableArray.append(skirtArray[])
}
Then in the functions for the table set up, I've referenced tableArray.
There are a number of ways you can do this, I'm guessing the arrays contain similar information (guessing from jeans and skirts its clothing).
I would have a single value for determining which product type is to be displayed, then you have a few options available....
enum TableMode {
case jeans, skirts, trousers, hats
}
The multiple products array way (not recommended)
class TableViewController: UITableViewController {
var tableMode: TableMode
func tableView(UITableView, numberOfRowsInSection: Int) -> Int {
switch tableMode {
case .jeans:
return jeansArray.count
/// and so on...
}
}
}
Another option would be to have a single array that will be used, just fill it with the correct data: (best method)
// the int value could be the category ID from your API
enum TableMode: Int {
case jeans = 13
case skirts = 2
trousers = 4
hats = 19
}
class TableViewController: UITableViewController {
var tableMode: TableMode
var products: [Products]()
override viewDidLoad() {
super.viewDidLoad()
// fetch all data from api/database where categoryId = .tableMode value
self.products = coreDataFetchResult
self.tableView.reloadData()
}
Last idea, fetch all products and just filter it as needed to show the current product type selection
class TableViewController: UITableViewController {
var tableMode: TableMode
var products: [Products]()
override viewDidLoad() {
super.viewDidLoad()
// fetch all data from core data
self.products = coreDataFetchResult.filter { $0.categoryId = self.tableMode }
self.tableView.reloadData()
}
The best option depends on where you are filling the arrays from. I wouldn't suggest having multiple arrays if you are only going to use one.

swift iOS filter on array containing uitableviews

func convertPointToIndexPath(_ point: CGPoint) -> (UITableView, IndexPath)? {
if let tableView = [tableView1, tableView2, tableView3].filter({ $0.frame.contains(point) }).first {
let localPoint = scrollView.convert(point, to: tableView)
let lastRowIndex = focus?.0 === tableView ? tableView.numberOfRows(inSection: 0) - 1 : tableView.numberOfRows(inSection: 0)
let indexPath = tableView.indexPathForRow(at: localPoint) ?? IndexPath(row: lastRowIndex, section: 0)
return (tableView, indexPath)
}
return nil
}
So i got this method, which converts a CGPoint into the indexPath of the given uitableView. I struggle with the filter-Method on the array which contains uitableViews.
I got an array outside of this method which contains any number of uitableViews. For example:
public var littleKanbanColumnsAsTableViews: [UITableView] = []
So i got to make a change inside of the method. Like this:
if let tableView = littleKanbanColumnsAsTableViews.filter({ $0.frame.contains(point)}).first { ... }
Now when i click on any tableView on the gui, i track the coordinates of the point and transform it on the belonging tableview with the frame.contains(point) method.
My problem is that the filter is not working, it always gives me the first tableView back, no matter which tableview is clicked. Why it doesn't work with my littleKanbanColumnsAsTableViews-Array?
One hint:
let tableView = littleKanbanView.littleKanbanColumnsAsTableViews[3]
When i indexing its direct then it works. But i want it depending on which tableView is containing the clicked point.
Here is my array with the tableViews, in this case the array contains 5 tableviews.
array containing tableviews
Now i want to filter the tableView out of them, which includes the point from tapping on this tableView. How can i achieve this?
For more understanding, i add the ui, here is it:
UI of my app
When i click on this tableView it works, because it is the first element in my array of tableViews. So for this case the convertPointToIndexPath-Method is working.
But when i scroll horizontally to the second tableView for example and click on that, it doesn't work. Because I think the method gives me always the first element back, but i thought it filters it with the given condition.
What is the problem, why doesn't work the condition{ $0.frame.contains(point)}? It have to localize the tableView when the coordinates of the point are tracked.
Preferred Solution:
In this case the moment the first satisfying condition is met, the rest of the elements are not traversed.
if let tableView = littleKanbanColumnsAsTableViews.first(where: { $0.frame.contains(point) }) {
}
Not so efficient solution:
In this case all the elements in the array are traversed to build an array of table views that satisfy the condition. Then the first element of that filtered array is chosen.
if let tableView = (littleKanbanColumnsAsTableViews.filter{ $0.frame.contains(point)}).first {
}

Is there any way to grab the text and detail text labels of multiple selected table view cells in swift?

I have some code right now that gets what cells were selected. The following is my code for doing that:
if let selectedUserRows = self.tableView.indexPathsForSelectedRows {
self.groupUserArray.append(selectedUserRows)
for index in selectedUserRows {
let text = groupUserArray[index.row]
print(text)
}
}
The new error is saying that the index is out of range!
I was trying to use the logic for grabbing one selected cells text but it does not work. So I was wondering if anyone knew how to grab multiple selected cell's text?
Any help would be appreciated!
self.tableView.indexPathsForSelectedRows returns a [IndexPath]? so if you obtain an array of all the rows and sections of you selected rows. I suppose you are using some sort of collection to populate your tableView (in my example I will call it textArray: [String] ) so you can do something like that to get all the text you need:
if let selectedUserIndexes = self.tableView.indexPathsForSelectedRows {
for index in selectedUserIndexes {
let text = textArray[index.row]
}
}

Cycle through array in iOS

In this app I save an NSOrderedSet of cards to a "Subject" entity in core data so that users can quiz themselves. The card flips and drags in tinder like fashion well but in the update card method I'm having some trouble. The current card displays fine as I set that in view did load (the first card of the NSOrderedSet's array property). When I drag and update however it immediately goes to the last card, for example if I have 5 cards in a deck it will start with the first then immediately go to the fifth. What would be the best way to update this method so that it will cycle through as desired?
I suppose I should pass it an index property like a tableViewDelegate method but if someone has done something like this before and has a better way that's greatly appreciated.
Thanks for the help like always.
func updateCard() {
//cycle through questions here
for var i = 0; i < (self.subject.cards?.count)!; i++ {
self.currentCard = self.subject.cards?.array[i] as? Card
self.draggableView.questionLabel.text = self.currentCard?.question
self.draggableView.answerLabel.text = self.currentCard?.answer
}
}
Currently, when you call updateCard the for loop is computing at computer-speed and you only get to see the last index.
Here's one option:
In your class, set a stored variable called selectedCardIndex as an implementation detail and then just increment and update the view in updateCard.
var selectedCardIndex = 0
func updateCard() {
self.selectedCardIndex += 1
self.currentCard = self.subject.cards?.array[self.selectedCardIndex] as? Card
self.draggableView.questionLabel.text = self.currentCard?.question
self.draggableView.answerLabel.text = self.currentCard?.answer
}

Resources