0

I am attempting to get the values from an NSSet in core data and append those values to an array of type String.

func addStepsToArray() {
    if let steps = entity.steps {
        for i in steps {
            recipeStep.append(String(i))
        }
    }
}

entity.steps is the list of steps tied to a core data entity. This is an NSSet. I am trying to copy those values to an array of type String.

@State var recipeStep: [String]

When trying to do this in my for in loop, I receive the following error: No exact matches in call to initializer

If I remove the conversion of "I" to String, I receive the following error:

Cannot convert value of type NSSet.Element (aka Any) to expected argument type String

Any idea on how to get this to work?

0

2 Answers 2

3

NSSet is defined in Objective C, which didn't have generics. It's an untyped collection, so you don't statically know anything about its elements.

As you've noticed, your i variable isn't a String, it's an Any.

You're confusing type coercion ("casting") with type conversion. If i were a Double, you could call String(i) to invoke an initializer which takes a double, and processes into a String.

You tried something similar by calling String(i), where you're making the Swift compiler find an initializer on String with the signitiure init(_: Any).

There is no such initializer. And besides, that's not what you want. You don't want to create a new String from a different kind of value. You already have a string, it's just "hidden" behind an Any reference.

What you're looking for is to do a down-cast, from Any to String:

func addStepsToArray() {
    if let steps = entity.steps {
        for i in steps {
            guard let step = i as? String else {
                fatalError("Decide what to do if the value isn't a String.")
            }
            recipeStep.append(i as String)
        }
    }
}

I'll warn you though, there are several issues/blemishes with this code:

  1. You're using a for loop to do what is ultimately just a mapping operation
  2. Your computation doesn't return its ouput, and instead indirectly achieves its goal through a side-effect on recipeStep
  3. Your computation doesn't take a its input as a parameter, and instead indirectly achieves its goal through a side-effect on entity
  4. i is conventionally expected to be an integer index of a for loop iterating over a sequence of numbers. Here it's an Any (a String at runtime)

Here's what I would suggest instead:

func getRecipeSteps(from entity: MyEntityType) -> [String] {
    guard let steps = entity.steps else { return [] }

    return steps.map { step in
        guard let stringStep = step as? String else { 
            fatalError("Decide what to do if the value isn't a String.")
        }

        return step
    }
}

Then in the rest of your code (and your tests), you can write self.recipeSteps = getRecipeSteps(from: myEntity). Elegant!

If you're certain that these entity.steps values can only ever be strings, then you can boil this down to a single map with a force-cast:

func getRecipeSteps(from entity: MyEntityType) -> [String] {
    entity.steps?.map { $0 as! String } ?? []
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the extremely detailed answer. I have tried all three approaches, but unfortunately the app crashes when landing on the page where the function executes (I am executing via .onAppear). The error is as follows: "Could not cast value of type 'RecipeTracker.StepsEntity' to 'NSString'". It looks like it is having trouble with the type casting. Thoughts?
Well you were operating on a misapprehension. You have some kind of StepsEntity which is clearly not a String.
StepsEntity itself is an entity that contains multiple attributes, but each step attribute within that entity is a String. Maybe if I tried something like entity.steps?.allObjects as? [StepsEntity]) and incorporated that into a map and it would return the correct data. I will give this a try later. I think the issue is it is trying to return all attributes in the entity, when it should only be returning the step attribute.
@nickreps So you probably want entity.steps.map { step in steps.theStringValueYouWant }. You haven't provided enough details in your question.
I ended up getting it to work using the following: func addSteps() { if let recipeSteps = (entity.steps?.allObjects as? [StepsEntity])?.sorted(by: { $0.stepNumber < $1.stepNumber }) { for index in recipeSteps { recipeStep.append(index.step ?? "") stepNumber.append(Int(index.stepNumber)) I am still going to mark yours as the accepted answer, as yours is correct based on what I described.
|
0

Just convert directly:

let set = Set(["1", "2", "3"])
let array = Array(set)
DDLog(set)//Set<String>)
DDLog(array)//[String]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.