MHDev MHDev - 20 days ago 5
iOS Question

How to print an array of dictionary values into a nice string in Swift

I have an array of

routine
objects thats are defined as:

struct Routine {
let routineName: String
let routineExercisesAndSets: [String: Int]
}

let routines: [Routine] = {
let firstRoutine = Routine(routineName: "Legs", routineExercisesAndSets: ["Squats":4,"Lunges":4,"Calf Raises":4])

let secondRoutine = Routine(routineName: "Chest", routineExercisesAndSets: ["Bench Press":4,"Press Ups":4,"Flyes":4,"Inclined Bench Press":4])

let thirdRoutine = Routine(routineName: "Arms", routineExercisesAndSets: ["Bicep Curls":4,"Hammer Curls":4,"Preacher Curls":4,"Tricep Extension":4,"Tricep Dips":4])

return [firstRoutine, secondRoutine, thirdRoutine]
}()


I am trying to return them
routineExercisesAndSets
array into a
UITableViewCell
as detail text and when using the array.description function I get this result in the tableView

My question is, how could I iterate through the routine objects and print out a 'pretty' formatted string that looks something like:

"Calf Raises x 4, Squats x 4, Lunges x 4..."


I can print out the individual key, value pairs on new lines using this code:

for i in routines{
for (key, value) in (i.routineExercisesAndSets)! {
print("\(key) x \(String(value))")
}
}


which produces:

Calf Raises x 4
Squats x 4
Lunges x 4
Bench Press x 4
Press Ups x 4
Flyes x 4
Inclined Bench Press x 4
Tricep Extension x 4
Tricep Dips x 4
Preacher Curls x 4
Hammer Curls x 4
Bicep Curls x 4


but I want to group them by their parent object so the exercises for a routine display on the same line so it looks like the example
"Calf Raises x 4, Squats x 4, Lunges x 4..."
.

Answer

You could make of a chained map and joined operation to construct a single String describing the exercise dictionary for a given exercise. E.g.

for routine in routines {
    print("Routine:", routine.routineName)
    print("--------------")
    print(routine.routineExercisesAndSets
        .map { $0 + " x \($1)" }
        .joined(separator: ", "))
    print()
}
/* Routine: Legs
   --------------
   Calf Raises x 4, Squats x 4, Lunges x 4

   Routine: Chest
   --------------
   Bench Press x 4, Press Ups x 4, Flyes x 4, Inclined Bench Press x 4

   Routine: Arms
   --------------
   Tricep Extension x 4, Tricep Dips x 4, Preacher Curls x 4, Hammer Curls x 4, Bicep Curls x 4 

                   */

Furthermore, instead of explicitly performing these "presentation" conversions upon each use, you could implement them as the return of the computed description property of Routine, as a part of letting Routine conform to CustomStringConvertible:

extension Routine: CustomStringConvertible {
    var description: String {
        return routineName + ":\n" + routineExercisesAndSets
            .map { $0 + " x \($1)" }
            .joined(separator: ", ")
    }
}

for routine in routines {
    print(routine) /* uses Routine:s implementation of 'CustomStringConvertible',
                      the computed property 'description`, specifically           */
    print()
}
/* Legs:
   Calf Raises x 4, Squats x 4, Lunges x 4

   Chest:
   Bench Press x 4, Press Ups x 4, Flyes x 4, Inclined Bench Press x 4

   Arms:
   Tricep Extension x 4, Tricep Dips x 4, Preacher Curls x 4, Hammer Curls x 4, Bicep Curls x 4

                   */

Given your comment below, it seems as if the properties routineName and routineExercisesAndSets of Routine are optionals. If this is the case, you naturally need to handle unwrapping them prior to accessing their (possibly existing) values. E.g., returning an empty description if any of the two properties is nil, and otherwise, the combined routine name and details description (as done in the non-optional examples above):

struct Routine {
    let routineName: String?
    let routineExercisesAndSets: [String: Int]?
}

extension Routine: CustomStringConvertible {
    var description: String {
        guard let name = routineName, let details = routineExercisesAndSets else { return "" }
        return name + ":\n" + details.map { $0 + " x \($1)" }.joined(separator: ", ")
    }
}