-1

I'm trying to filter my json data by IDs (trying mark some favourites and filter using it)

struct workoutList : Codable {
  let id : Int
  let title : String
  let tag : String
}


func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
  var selectedGroup = [workoutList]()
  let workoutFav = [1,10,100]

  if libraryFilter == 0 {
    // This works because I'm filtering based on 1 specific item
    selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text } 
  } else if libraryFilter == 1 {
    // Here I want to filter and show only the favorites
    selectedGroup = jsonErgWorkouts.filter { $0.id } // 
    print("selectedGroup:\(selectedGroup)")
  }
  return selectedGroup
}

in the above code, the filter works when I have 1(one) something specific item to filter and then I get the entire json array with that tag.

Now I want to implement a favorite list, where the user selects for example ID == [1, 10 ,100] as their favourite.

How can I use the filter command to do it? I tried a few things and searched through SO (but doesn't work). Most of the answers are based on filtering based on specific items eg:

selectedGroup = jsonErgWorkouts.filter { workoutFav?.contains($0.id) }

edit: (omitted that I am using/storing the favourites in userDefaults. This code gives the error of "type of expression is ambiguous without more context"

    func selectedWorkoutGroup(libraryFilter: Int, jsonErgWorkouts:[workoutList], workoutGroupBox: UITextField) -> [workoutList] {
      var selectedGroup = [workoutList]()
      UserDefaults.standard.set([1,10,100], forKey: "workoutFavorite")
      
      /// This one gets stored as [Any] so I cast it to [Int]
      let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]

     if libraryFilter == 0 {
        // This works because I'm filtering based on 1 specific item
        selectedGroup = jsonErgWorkouts.filter { $0.tag == workoutGroupBox.text } 
      } else if libraryFilter == 1 {
         selectedGroup = workoutFav.flatMap { favouriteId in // for each favourite ID
         jsonErgWorkouts.filter { $0.id == favouriteId } // This returns Error "type of expression is ambiguous without more context"
         } // flatMap joins all those arrays returns by "filter" together, no need to do anything else

        print("selectedGroup:\(selectedGroup)")
      }
      return selectedGroup
    }

Final Solution: Changing from This

let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]

to This (notice the as! instead of as?)

let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]

works using @sweeper's answer. Thanks

Update: Figured out why this error occurred "type of expression is ambiguous without more context" when casting the output of UserDefaults as? [Int] and had to use as! [Int]

But using as! [Int] force unwrapping it causes app to crash if the user did not have any favorites saved into the UserDefault. (Which I then had to code around) like below

var workoutFav = [Int]()
  
if !(UserDefaults.standard.array(forKey: "workoutFavorite") == nil) {
   workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as! [Int]
}

Which was then simplified and removed the force unwrapping based on this SO https://stackoverflow.com/a/37357869/14414215 to become this one-line

  let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int] ?? [Int]()
app4g
  • 670
  • 4
  • 24

2 Answers2

1

You need to do that filter for each id in the favourites array. You get an array of arrays as a result. To get the final array, you need to join those arrays to a single array. This "map each thing to an array and join the arrays" operation is what a flatMap does:

workoutFav.flatMap { favouriteId in // for each favourite ID
    jsonErgWorkouts.filter { $0.id == favouriteId } // find workouts that match the ID
} // flatMap joins all those arrays returns by "filter" together, no need to do anything else
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Somehow it's not allowing me to execute it.```cannot convert value of type [Any] to expected argument type 'Int'``` I tried to cast it as Int ```$0.id == favoriteID as? Int``` but didn't work. Why is it that favoriteID is of type [Any]? – app4g Dec 18 '20 at 11:39
  • @myjunk [I can't reproduce that error](https://repl.it/@Sweeper777/NextLatestComputers#main.swift). Please show a [mcve]. Either you incorrectly applied my answer, or you haven't showed the full picture in your question. – Sweeper Dec 18 '20 at 11:44
  • you're right.. I think it's has to do with the way I store my Array. (I'm storing it into UserDefault and it's being stored as [Any]. I tried to cast it to ```as? [Int]``` but not working.. I'll update the code.. Thanks for help. – app4g Dec 18 '20 at 11:47
  • @myjunk Are you casting `favouriteID` as `[Any]`? You should cast `workoutFav` instead. Looks like the filtering favourites problem is solved isn't it? If casting `workoutFav` still doesn't work, that would be a **[different question](https://meta.stackexchange.com/questions/43478/exit-strategies-for-chameleon-questions "at Stack Exchange sites, “chameleon questions” are not quite welcome")**, consider posting it separately. Remember to include a [mcve] :) – Sweeper Dec 18 '20 at 11:55
  • did this ```let workoutFav = UserDefaults.standard.array(forKey: "workoutFavorite") as? [Int]``` then your solution ```selectedGroup = workoutFav.flatMap { favouriteId in jsonErgWorkouts.filter { $0.id == favouriteId } }``` gives ```type of expression is ambiguous without more context``` is this a diff issue then? – app4g Dec 18 '20 at 12:03
  • thanks for your help. Managed to figure it out. I was using ```workoutFav as? [Int]``` and it gave error. When I did it as ``` workoutFav as! [Int]``` then it started to work using your answer. I'll mark this as correct. Thanks!! – app4g Dec 18 '20 at 12:18
1

First thing first please give a struct name with a capital so you can distinguish between instance of it. Second you need to have new array where you will store each favorite, and store permanently that array, core data or some base on server, form there you will fetch favorites. The better way is to add property like isFavorite: Bool that is false by default, and if user change it you can set it to be true, in that way you can avoid using ids for that and you can store whole workout's in one array to core data or base that you use, after that you can fetch from there with

 let favorites = workouts.compactMap { $0.isFavorite == true }

Here you go in that way, but just to mention it highly recommended that you store those type of data outside User defaults.

struct Fav {
        let name: String
        let id: String
    }
    
    let df = UserDefaults.standard
    let jk = ["aaa", "bbb", "cccc"]
    df.setValue(jk, forKey: "favorites")
    
    let fav1 = Fav(name: "zzz", id: "aaa")
    let fav2 = Fav(name: "bbb", id: "qqq")
    let favs = [fav1, fav2]
    
    let favIDs = df.value(forKey: "favorites") as? [String]
    
    favIDs?.forEach({ (id) in
        let f = favs.filter({$0.id == id}) // here it is
    })
MMDev11070
  • 131
  • 5
  • The list of workouts are stored as a JSON file within the app and I'm thinking of storing the favorite list within UserDefaults (since it's gonna be small qty of IDs) – app4g Dec 18 '20 at 11:56
  • Thanks. I have yet to learn how to use CoreData but that would be the next step. – app4g Dec 18 '20 at 12:58