Using firstIndex(of: Element) in Swift - ios

I have the following code in Swift 4, on Xcode 10
var newCard = deck.randomElement()!
deck.remove(at: deck.firstIndex(of: newCard)!)
dealtCards.append(newCard)
I'm trying to take a random element from deck: [SetCard] and place it into dealtCards: [SetCard]
However, Xcode gives me the following error:
I've been looking through the documentation, but as far as I can tell, func firstIndex(of element: Element) -> Int? is a method that exists in Array, so I don't understand why Xcode wants me to change 'of' to 'where', when the function has the exact behaviour I want.
What am I missing here?

The problem has been already explained in the other answers. Just for the sake of completeness: If you choose a random index instead of a random element in the array then the problem is avoided and the code simplifies to
if let randomIndex = deck.indices.randomElement() {
let newCard = deck.remove(at: randomIndex)
dealtCards.append(newCard)
} else {
// Deck is empty.
}

Your deck array elements should conform to Equatable protocol,
For example as String conforms to Equatable protocol, the below code snippet works,
var arr = ["1", "2", "3"]
let newCard = arr.randomElement()!
arr.remove(at: arr.firstIndex(of: newCard)!)

The problem is that your SetCard type has not been declared Equatable. Thus there is no way to know if a card is present or what its index is, because the notion of a card that “matches” your newCard is undefined.
(The compiler error message is not as helpful on this point as it could be. That is a known issue. Bug reports have been filed.)

Related

How to find value difference of two struct instances in Swift

I have two instances from the same struct in Swift. I need to find out key-values that have the same keys but different values.
For example:
struct StructDemo {
let shopId: Int
let template: String?
}
let a = StructDemo(shopId: 3, template: "a")
let a = StructDemo(shopId: 3, template: "different a")
// My expectation is to return the change pairs
let result = [template: "different a"]
My approach is as show below but comes errors.
static func difference(left: StructDemo, right: StructDemo) -> [String: Any]{
var result:[String: Any] = [:]
for leftItem in Mirror(reflecting: left).children {
guard let key = leftItem.label else { continue }
let value = leftItem.value
if value != right[key] { // This is the problem. Errror message: Protocol 'Any' as a type cannot conform to 'RawRepresentable'
result[key] = right[key]
}
}
}
Appreciate for any suggestion and solutions.
Thank you
The problem that you are seeing is that you referred to
right[key]
but right is a StructDemo and is not subscriptable. You can't look up fields given a runtime name. Well, you can with Mirror which you correctly used for left, but you did not mirror right.
Using Mirror will lead to other issues, as you will have expressions with static type Any where Equatable will be required in order to compare values.
IMHO, your best bet is to avoid a generic, reflective approach, and just embrace static typing and write a custom difference functions that iterates all the known fields of your type. Hard coding is not so bad here, if there is only one struct type that you are trying to diff.
If you have a handful of struct types each needing diffs then that might be a different story, but I don't know a good way to get around the need for Equatable. But if you have a ton of diffable types, maybe you want dictionaries to begin with?

How to cast an typed array to one of the types protocols?

I have this in my playground:
func foo(printables: [Printable]) {
// do something
}
enum MenuOptions: String, Printable {
case ChangePickup = "Change Pickup Time"
case RequestSupport = "Request Support"
var description: String {
get {
return self.rawValue
}
}
}
var menuOptions: [MenuOptions] = [.ChangePickup]
foo(menuOptions)
let printables = menuOptions as [Printable]
Both last lines give a compiler error. I would expect menuOptions to be implicitly casted to [Printable] but the compiler complains with a [MenuOptions] is not convertible to [Printable] error. Am I missing something?
If I do menuOptions as! AnyObject as! [Printable] the compiler doesn't complain and the code works properly, but this seems dirty. Funnily enough, doing foo(menuOptions.map { $0 }) also works! Is this just a compiler bug?
You are trying to create a generic function constrained to objects that conform to the Printable protocol. Just define it differently:
func foo<T: Printable>(printables: [T]) {
// do something
}
The function will now work in your context. It will take an array of any object type that conforms to Printable.
Here are the relevant docs on Generics, specifically Type Constraints.
As to why this works, but your definition doesn't - this is just the Swift syntax. Printable is a protocol, but T is a type (generic, but still a type). You want your function to accept any type (T) that conforms to a protocol (<T: Protocol>). This is just how the language has been designed.
Using menuOptions as! AnyObject as! [Printable] may work in this example, but it is a bad practice. Using as! is like telling the compiler "trust me, this will work." This is fine in limited cases, but sets you up for trouble in the long run. Using as! turns off the compiler's type checking, which means if there is a problem it will happen at runtime, and your app will crash.
Again, I highly suggest reading the docs on Type Constraints, which I linked above. It explains it in detail.
I found that this is working. It seems that Swift compiler is not that smart.
var menuOptions: [MenuOptions] = [MenuOptions.ChangePickup]
let printables:[Printable] = menuOptions as! [Printable]
foo(printables)
I don't know why these are not working
foo(menuOptions)
foo(menuOptions as! [Printable])

"fatal error: array cannot be bridged from Objective-C"—Why are you even trying, Swift?

I have declared a Swift protocol:
protocol Option {
var name: String { get }
}
I declare multiple implementations of this protocol—some classes, some enums.
I have a view controller with a property declared as so:
var options: [Option] = []
When I try and set this property to an array of objects that implement the Option protocol in another VC's prepareForSegue, I get a runtime error:
fatal error: array cannot be bridged from Objective-C
Why doesn't this work? The compiler has all the information it needs, and I don't understand what Objective-C has to do with it at all—my project contains only Swift files, and these arrays aren't coming in or out of any framework methods that would necessitate them being bridged to NSArray.
I have found a solution. It is quite... unsatisfying, but it works. Where I set the array on the destination view controller I do:
destinationViewController.options = options.map({$0 as Option})
the compiler knows I'm passing in an Array of things that implement Option
You've let slip there a very revealing remark, which suggests the source of the issue. An "Array of things that implement Option" is not an Array of Option.
The problem is with the type of options back at the point where you create it (in prepareForSegue). You don't show that code, but I am betting that you fail to cast / type it at that point. That's why the assignment fails. options may be an array of things that do in fact happen to adopt Option, but that's not enough; it must be typed as an array of Option.
So, back in prepareForSegue, form your options like this:
let options : [Option] = // ... whatever ...
Now you will be able to assign it directly to destinationViewController.options.
Here's a quick test case (in a playground; I detest playgrounds, but they can have their uses):
protocol Option {
var name : String {get}
}
class ViewController : UIViewController {
var options : [Option] = []
}
enum Thing : Option {
var name : String {
get {
return "hi"
}
}
case Thing
}
let vc = ViewController()
let options : [Option] = [Thing.Thing]
vc.options = options // no problem
(I also tested this in an actual app with an actual prepareForSegue, and it works fine.)
I was having the same problem and fixed it marking my protocol with #objc, in your case it would look like this
#objc protocol Option {
var name: String { get }
}
Got the solution from this answer
This one also works fine
destinationViewController.options = options.map{$0}

componentsSeparatedByString method in Swift

I had some troubles last night accessing the elements of an array created via the componentsSeparatedByStringMethod. My goal is to extract some information from an url content variable.
var arr = urlContent.componentsSeparatedByString("<span class=\"Some HTML class\">")
I printed arr to the log, it works perfectly
I'd like to access the second element of the array arr, in order to get the info I need (the information right after the span tag). I thought:
var info = arr[1]
would work, but it doesn't. I get an error message saying that the subscript method doesn't work for an object of type "AnyObject" or something similar. So the problem is arr is not an Array Object type but an AnyObject type. I tried to convert it to an array but it didn't work either.
Could anybody help through this little trouble? :)
In xcode 6.1.1 componentsSeperatedByString method returns [AnyObject]. You have to cast it to [String] like so:
if let arr = urlContent.componentsSeparatedByString("<span class=\"Some HTML class\">") as? [String] {
// arr is now [Sstring]
}
In xcode 6.3 beta this method returns [String] so cast is not needed.

error: Generic parameter 'R.Generator.Element' cannot be bound to non-#objc protocol type 'AnyObject'

I am querying HealthKit and saving it to CoreData. I fetch the data in a separate class. In TableViewController I append the data to an array:
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
xAxisDatesArray.append(cdFetchWeight.queryCoreDataDate())
yAxisValuesArray.append(cdFetchWeight.queryCoreDataData())
and pass it at tableView.dequeueReusableCellWithIdentifier
myCell.xAxisDates = xAxisDatesArray[indexPath.row]
myCell.yAxisValues = yAxisValuesArray[indexPath.row]
In UITableViewCell I initialise the variables (yAxisValues, xAxisDates) and pass them into a charting library which takes the x and y values and plot a chart.
class TableViewCell: UITableViewCell, TKChartDelegate {
var xAxisDates = []
var yAxisValues = []
plot....
I need to get the min and max values of yAxisValues so that I can set the appropriate y-axis range to the data.
I have tried to get the min and max with the following code:
func rangeMinAndMax(){
let minYvalue = minElement(yAxisValues)
let maxYvalue = maxElement(yAxisValues)
}
But this generates the error: Generic parameter 'R.Generator.Element' cannot be bound to non-#objc protocol type 'AnyObject'
- Question: Why and how can I fix it?
Any help would be much appreciated !
The thing to do in a situation like this is to throw away all the misleading dross and boil it down to the simplest possible case:
let arr : [AnyObject] = [1,2,3]
let min = minElement(arr) // same error: "Generic parameter blah-de-blah..."
So, you see, minElement doesn't work on an array of AnyObject, and that's what the error is telling you. And the reason is obvious: an AnyObject is not a Comparable. There is no "minimum" for a bunch of AnyObject things; the entire concept of one AnyObject being "less than" another AnyObject is undefined. You need to cast your array down to an array of something that minElement can work on, namely a Comparable of some kind.
For example, in that code, I can fix the problem like this:
let arr : [AnyObject] = [1,2,3]
let min = minElement(arr as [Int])
That is the sort of thing you need to be doing. Of course, what you cast down to depends upon what these elements actually are. It looks to me as if will probably be Double and NSDate respectively, but that's just a guess; I don't know what's in your arrays. You do (presumably). Note that an NSDate is not a Comparable so you will have a bit more work to do with that one.

Resources