Trying to loop over an array of Custom class objects - arrays

I have a data mod that looks like this:
class CurrencyModel {
var currencyName = ""
var currencyCode = ""
}
I'm trying to loop over all of these objects and create an array of currencyNames.
Here is how I'm trying to do that:
var currencies = [CurrencyModel]()
override func viewWillAppear(_ animated: Bool) {
for names in currencies{
let name = currencies[names].currencyName
namesArray.append(name)
print(namesArray)
}
}
I keep getting this error:
Cannot subscript a value of type '[CurrencyModel]' with an index of type 'CurrencyModel'

currencies is a [CurrencyModel] (an array of CurrencyModel).
The loop for names in currencies iterates that array. names will be each individual CurrencyModel object in the array.
So the line:
let name = currencies[names].currencyName
should be:
let name = names.currencyName
Of course the entire for loop can be replaced with one line:
let namesArray = currencies.map { $0.currencyName }

The problem is here
let name = currencies[names].currencyName
as currencies is an array ( [CurrencyModel]) to subscript it needs an integer value like currencies[0] , but you send names instead which is of type CurrencyModel
You need
var namesArray = [String]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
namesArray = currencies.map{$0.currencyName}
}
OR
currencies.forEach { namesArray.append($0.currencyName)}
OR
for names in currencies { // not perferred
namesArray.append(names.currencyName)
}
//
Edit: then use
namesArray = Array(currencies.map{$0.currencyName}.prefix(1))

Related

Swift all characters in texview in an array

I am trying to get all characters in an array, when user types something.
In my codes; when I type “hello” and print it, I get:
arrRegular: [“h”]
arrRegular: [“e”]
arrRegular: [“l”]
arrRegular: [“l”]
arrRegular: [“o”]
How can I make all characters in one array, like [“h”, “e”, “l”, “l”, “o”]
My codes:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
var arrRegular = [String]()
var arrBold = [String]()
var arrCombined = [String]()
if boldFont == false {
arrRegular.append(text)
} else {
arrBold.append(text)
}
arrCombined.append(contentsOf: arrRegular)
arrCombined.append(contentsOf: arrBold)
print(arrCombined)
return true
}
your var var arr = [String]() is local and is created every call of shouldChangeTextIn which is called every type of a character
You need to
let arr = Array(textView.text!)
or
let arr = textView.text.map { "\($0)" }
print(arr)
let text = textView.text ?? ""
let array = Array(text)

Swift - Shuffling a filtered array of structs doesn't change originating array

I'm writing a card game with a number of cards collected together in one array.
The data structure of the cards is the same, but the data is different.
Note: My shuffle does work.
I'm wanting to filter this array, and only shuffle the cards I have filtered.
However, whilst I can shuffle the cards, I've noticed that my originating array of cards does not change at all.
I believe that this issue is caused because my card model is a struct and not a class.
More background info.
Whilst the data for each card is different, the structure is exactly the same; both types have a name, and a numeric value.
This is modelled thusly;
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
I create my cards in a static function like this:
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx, owner: nil)
properties.append(property)
}
return properties
}
}
Okay, so now I only want to shuffle my property cards within this cards array.
So in my XCTest file first filter all cards that are of type: property
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
This calls:
// Inside my FSCardAPI
public static func shuffleCards(cards:[FSCard]) -> [FSCard] {
let shuffled:[FSCard] = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: cards) as! [FSCard]
return shuffled
}
The issue:
When I run my XCTest, I notice that whilst the propertyCards is shuffled, my gameModel.cards are not shuffled;
Test Case 'testShuffleProperties' started.
// contents of `propertyCards`
[FSCard(type: FSCardType.property, name: "Property #4", value: 4, owner: nil),
FSCard(type: FSCardType.property, name: "Property #16", value: 16, owner: nil),
// .. etc
// contents of `gameModel.cards`
[FSCard(type: FSCardType.property, name: "Property #1", value: 1, owner: nil),
FSCard(type: FSCardType.property, name: "Property #2", value: 2, owner: nil),
// .. etc
Summary
I have an array of cards (ie: 30 cards)
Cards are separated by types (ie: property)
I want to filter the property cards and shuffle those cards only
I want the original array to reflect these changes
In my test, the array gets shuffled; but the original array remains the same.
One way I can think of is that I do all the shuffling, and then sort the array by card types and then update the gameModel.cards, but that seems a bit over the top.
Another obvious way I can think about solving this is to change my struct to a class or; perhaps I need another layer in between the struct?
So my query is:
How do I filter an array of structs, only shuffle those results and change the state of the originating array?
Many thanks
Edit:
The properties array is the only item to shuffle.
If I add another array to the list it should not shuffle the entire contents.
IE; if I do this:
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
let cheqs:[FSCard] = FSCardAPI.createCheques()
cards.append(contentsOf: properties)
cards.append(contentsOf: cheqs)
return cards
}
I should only shuffle the property cards within themselves without impacting the cheqs.
I guess I could make it easier and just have 2 arrays, but at the same time I think there is no reason to do this because the data structure is the same.
You are not assigning to gameModel.cards and not actually changing the array. so I would not expect gameModel.cards to be shuffled.
You should either just assign the shuffled array back to gameModel.cards like so:
func testShuffleProperties() {
var propertyCards = gameModel.cards.filter { (fs:FSCard) -> Bool in
return (fs.type == .property)
}
propertyCards = FSCardAPI.shuffleCards(cards: propertyCards)
gameModel.cards = propertyCards
print(propertyCards)
print(" ---- ")
print(gameModel.cards)
}
Or if you want to mutate the array directly you should look at passing the cards by reference, or in Swift.. using an inout parameter. An inout parameter passes the value to the function by reference, or passed the memory addres of the value, so that it can be modified directly. Check the shuffle cards function below (how it is defined and used)
(I replaced the shuffle function with a swift extension for ease of use in a playground)
extension MutableCollection where Indices.Iterator.Element == Index {
/// Shuffles the contents of this collection.
mutating func shuffle() {
let c = count
guard c > 1 else { return }
for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
let d: IndexDistance = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
guard d != 0 else { continue }
let i = index(firstUnshuffled, offsetBy: d)
swap(&self[firstUnshuffled], &self[i])
}
}
}
extension Sequence {
/// Returns an array with the contents of this sequence, shuffled.
func shuffled() -> [Iterator.Element] {
var result = Array(self)
result.shuffle()
return result
}
}
enum FSCardType: Int {
case property
case anotherType
case yetAnotherType
}
// Card Model
struct FSCard {
let type: FSCardType
let name: String
let value: Int
var description: String {
return ("Name: \(self.name) Value: \(self.value), Type: \(self.type)")
}
}
class FSCardAPI: NSObject {
public static func createCards() -> [FSCard] {
var cards:[FSCard] = [FSCard]()
let properties:[FSCard] = FSCardAPI.createProperties()
cards.append(contentsOf: properties)
return cards
}
public static func shuffleCards(cards:inout [FSCard]) {
cards = cards.shuffled()
}
fileprivate static func createProperties() -> [FSCard] {
var properties:[FSCard] = [FSCard]()
for idx in 1...30 {
let property:FSCard = FSCard(type: .property, name: "Property #\(idx)", value: idx)
properties.append(property)
}
return properties
}
}
var cards = FSCardAPI.createCards()
FSCardAPI.shuffleCards(cards: &cards)
Output:
Read up on inout parameters here in the In-Out parameters section.
Exert from the documentation:
Function parameters are constants by default. Trying to change the value of a function parameter from within the body of that function results in a compile-time error. This means that you can’t change the value of a parameter by mistake. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.
You write an in-out parameter by placing the inout keyword right before a parameter’s type. An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value. For a detailed discussion of the behavior of in-out parameters and associated compiler optimizations, see In-Out Parameters.
You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an in-out parameter, to indicate that it can be modified by the function.
EDIT: Try this updated shuffle function, pass in the whole array and see if does what you need.
public static func shuffleCards(cards:inout [FSCard]) {
let propertyCards = cards.filter({ $0.type == .property })
let indexes = propertyCards.map { Int(cards.index(of: $0)!) }
let shuffledCards = propertyCards.shuffled()
for (idx, val) in indexes.enumerated() {
cards[val] = shuffledCards[idx]
}
}
The problem is that after you shuffle your property cards, you are not doing anything with them. You should replace property cards in your original card list with the ones in your shuffled property card list.
var propertyCardsShuffledOriginalArray = originalArray.map {
var card = $0
if $0.type == .property {
card = shuffledPropertyCards.first as! FSCard
shuffledPropertyCards.removeFirst()
}
return card
}
propertyCardsShuffledOriginalArray is what you need

Filtering arrays for use with UISearchBar

I have a table view which displays a user's Name, Company Name and Photo (PFFile). Each tableView row I have has all of this information in it.
I am using UISearchBarDelegate and IB to implement a search function to filter by the user's Name. It is finding the correct user but I have not been able to also update the company photo.
How do I filter the other arrays? The items I need from the arrays will be at the same index as the ones taken from the user's Name array.
EDIT: I am trying a different data structure and am receiving array index out of range, updated code below:
var filterArray = [User]() //<-- globally declared
var userArray = [User]() //< Global
class User {
var name: String?
var company: String?
init (name: String?, company: String?) {
self.name = name
self.company = company
}
}
//In a class which populates the search arrays
for object in unwrappedSucceeded {
let username = object.valueForKey("username") as! String
let companyName = object.valueForKey("companyName") as! String
let user = User(name: username, company: companyName)
userArray.append(user)
}
//tableViewController
func searchBar(searchBar: UISearchBar, textDidChange searchText: String) {
filterArray.removeAll(keepCapacity: false)
if searchText.characters.count != 0 {
isSearch = true
self.search(searchText)
} else {
isSearch = false
}
}
func search(text: String) -> Void {
filterArray = userArray.filter({$0.name == text})
}
//In cellForRowAtIndexPath
cell.usernameCell.text = filterArray[indexPath.row].name //ARRAY INDEX OUT OF RANGE
Like I said you strongly recommend to group each user's info into one big container, therefore we could use array of struct or class, then it comes easier to filter.
schematic for the container:
struct Container
{
var username:String?
var companyName:String?
var photo:UIImage?
}
your main array will be : var arrayofData = [Container]()
Now when you are query your objects from parse, inside of your query function
// after you called the findObjectsWithBackgroundBlock()
// let's assume you check for error and if the [PFObject] is empty or not
for one in objectsFromParse
{
let photoToget = one["Photo"] as! PFFile
// next step should be to get the image data right :)
{
// let's assume that is the block when get the image data right:)
// check your data and assign it to some UIImage
// then
let userRepresentation = Container() //<-- we are creating a single object representation for each user
let username = one["username"] as! String //<--data we got from Parse
let companyName = one["companyName"] as! String
let userImage = //the UIImage which contains the data
userRepresentation.username = username
userRepresentation.companyName = companyName
userRepresentation.photo = userImage
// then we append
arrayOfData.append(userRepresentation)
}
}
Now we have all data into our array, so let's filter by username and also I hope you configure your tableView so when you have data from filter or regular array.
var filterArray = [Container]() //<-- globally declared
func search(text: String) -> Void
{
filterArray = arrayOfData.filter(){ (Container) -> Bool in
let range = Container.name!.rangeOfString(text, options:NSStringCompareOptions.CaseInsensitiveSearch) return range != nil }
// then you are good to go
}
let arr1 = [10,20,40]
let e1 = arr1.enumerate()
let arr2 = ["a","b","c"]
let f1 = e1.filter { $0.element % 20 == 0 }
let f2 = arr2.enumerate().filter { j, _ in
f1.contains { i, _ in
i == j
}
}
print(f1.map{$0.element}, f2.map{$0.element})
// [20, 40] ["b", "c"]
now you have both arrays "filtered". the best, what you can do is redesigning your data model!

Cannot convert value of type 'Meme!' to expected argument type '#noescape (Meme) throws -> Bool'

Here is the code:
#IBAction func deleteMeme(sender: UIBarButtonItem) {
if let foundIndex = MemeRepository.sharedInstance.memes.indexOf(selectedMeme) {
//remove the item at the found index
MemeRepository.sharedInstance.memes.removeAtIndex(foundIndex)
navigationController?.popViewControllerAnimated(true)
The error happens at the .indexOf method at (selectedMeme).
Cannot convert value of type Meme! to expected argument type #noescape (Meme) throws -> Bool
Meme! is a struct for my app. How do I work through this?
struct Meme {
var topText : String!
var bottomText: String!
var image: UIImage!
var memedImage: UIImage!
init(topText: String, bottomText: String, image: UIImage, memedImage: UIImage) {
self.topText = topText
self.bottomText = bottomText
self.image = image
self.memedImage = memedImage
The error message is misleading. What you actually need is to provide the compiler a way to compare two Meme instances and decide upon which criteria those instances are equal.
Let's say you want two instances having the same name property to be treated as equal.
We make the struct conform to Equatable and we also create an == operator that compares two structs by their name property:
struct Meme:Equatable {
var name:String!
}
func ==(lhs: Meme, rhs: Meme) -> Bool {
return lhs.name == rhs.name
}
Now we can use indexOf with a Meme instance:
let doge = Meme(name: "doge")
let lolcat = Meme(name: "lolcat")
let memes = [lolcat, doge]
if let dogeIndex = memes.indexOf(doge) {
print(dogeIndex) // 1
}
If you wanted to compare two instances not by their name property but by their uniqueness, then you would have to make the struct conform to Hashable and use a unique hashValue property returning an Int:
struct Meme:Hashable {
var name:String!
var hashValue: Int {
return self.name.hashValue
}
init(name: String) {
self.name = name
}
}
func ==(lhs: Meme, rhs: Meme) -> Bool {
return lhs.hashValue == rhs.hashValue
}
let doge = Meme(name: "doge")
let lolcat = Meme(name: "lolcat")
let memes = [lolcat, doge]
if let dogeIndex = memes.indexOf(doge) {
print(dogeIndex) // 1
}
In this example the hashValue is made from self.name, so two different instances of Meme with a same name property will be considered equal. If you don't want that, use another source for the hash value.
Note: in Swift 3, indexOf has become index(of:), so for this example we would change memes.indexOf(doge) to memes.index(of: doge).
If you want to put the comparison inside the indexOf method itself, do it like this:
if let foundIndex = MemeRepository.sharedInstance.memes.indexOf({
UIImagePNGRepresentation($0.memedImage) == UIImagePNGRepresentation(selectedMeme.memedImage)})
Probably not the best way to compare images. If you know the images are the same object, you can use:
.indexOf({$0.memedImage == selectedMeme.memedImage})
but if you want to compare them pixel by pixel or compare the same image scaled to different sizes, that is a little more complicated.

Change array back to dictionary - SWIFT

I have a for loop that creates a dictionary and then I append the dictionary to an array. I append the dictionary to an array because I don't know how to add more than one value with the same key, when I do that in the for loop the key / value pair is just updated and the old key / value pair is deleted What is the best way to change the array back to a dictionary?
import UIKit
class ViewController: UIViewController {
var jobTitle = ""
var jobDescription = ""
var dict:[String: AnyObject] = ["jobTitle": "jobTitle", "jobDescription": "jobDescription"]
var tArray = [[String: AnyObject]]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
for var i = 0; i < 3; ++i {
jobTitle = "job1"
jobDescription = "Desc1"
dict["jobTitle"] = "job1"
dict["jobDescription"] = "Desc1"
tArray.append(dict)
}
println("\(tArray)")
}
}
Like such, where you have more than one value associated with each key:
let jobnames = ["j1", "j2", "j3"]
let jobdescs = ["d1", "d2", "d3"]
var dict : [String:[String]] = [:]
for index in 0..<3 {
if nil == dict["jobTitle"] { dict["jobTitle"] = [] }
if nil == dict["jobDesc" ] { dict["jobDesc" ] = [] }
dict["jobTitle"]!.append(jobnames[index])
dict["jobDesc" ]!.append(jobdescs[index])
}
Here is the output:
You call the same assignmenta such as
jobTitle = "job1"
at every iteration of the loop. Of course the variable will always contain the same value. The same is true for dict. It is an ivar, so you keep overwriting it.
What you want is to create a new collection of type [String: AnyObject] to add to your array.
let newDict:[String : AnyObject] = [titleKey : titleText,
descriptionKey : descriptionText]
tArray.append(newDict)

Resources