-1

I've got these 2 JSON that share a field userId

This one:

{
    "oldest": "2019-01-24T00:00:00+00:00",
    "activities": [
        {
            "message": "<strong>Henrik</strong> didn't resist a guilty pleasure at <strong>Starbucks</strong>.",
            "amount": 2.5,
            "userId": 2,
            "timestamp": "2019-05-23T00:00:00+00:00"
        },
        {
            "message": "<strong>Johan</strong> made a roundup.",
            "amount": 0.32,
            "userId": 3,
            "timestamp": "2019-05-22T00:00:00+00:00"
        },
        {
            "message": "<strong>You</strong> didn't resist a guilty pleasure at <strong>Starbucks</strong>.",
            "amount": 15,
            "userId": 1,
            "timestamp": "2019-05-21T00:00:00+00:00"
        }
]

And this one:

[
    {
        "userId": 1,
        "displayName": "Mikael",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/mikael.jpg"
    },
    {
        "userId": 2,
        "displayName": "Henrik",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/henrik.jpg"
    },
    {
        "userId": 3,
        "displayName": "Johan",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/johan.jpg"
    },
    {
        "userId": 4,
        "displayName": "Erik",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/erik.jpg"
    },
    {
        "userId": 5,
        "displayName": "George",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/george.jpg"
    },
    {
        "userId": 6,
        "displayName": "Daniel",
        "avatarUrl": "http://qapital-ios-testtask.herokuapp.com/avatars/daniel.jpg"
    }
]

Now I have the first one sorted out like this:

struct Root: Decodable {
    var oldest: Date
    var activities: [Activity]
}

struct Activity: Decodable {
    var message: String
    var amount: Float
    var userId: Int
    var timestamp: Date
}

Notice that the userId var in Activity (1st JSON) also appears on the 2nd JSON. I'm using these structs to populate my TableViewController.

What I ultimately need is to have the avatar image show up next to the message and date label (check image). For this I think I would need to connect/relate (don't know the word for it) the 1st JSON with the 2nd JSON, correct me if I'm wrong. Would I also need another struct for User?

This is my networking code:

let userJSONURLString = "https://qapital-ios-testtask.herokuapp.com/users"
        let activitiesJSONURLString = "https://qapital-ios-testtask.herokuapp.com/activities?from=2016-05-23T00:00:00+00:00&to=2019-05-23T00:00:00+00:00"
        guard let userURL = URL(string: userJSONURLString) else { return }
        guard let activitiesURL = URL(string: activitiesJSONURLString) else { return }

        URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
            // perhaps check err
            // also perhaps check response status 200 OK

            guard let data = data else { return }

            do {
                // Activities
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .iso8601
                let result = try decoder.decode(Root.self, from: data)
                self.activities = result.activities

                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            } catch {
                print("Error serializing json: ", error)
            }

        }.resume()

And this is my cellForRowAt (if you need it):

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
        let activity = activities[indexPath.row]

        // Amount
        cell.amountLabel.text = String(format: "$%.2f", activity.amount)

        // Message
        let formattedString = activity.message.htmlAttributedString().with(font:UIFont(name: "Helvetica Neue", size: 15)!)
        cell.descriptionLabel.attributedText = formattedString

        // Date
//        cell.dateLabel

        // Avatar


        return cell
    }

Updated code:

struct Activity: Decodable {
    var message: String
    var amount: Float
    var userId: Int
    var timestamp: Date
    var avatar : UIImage = UIImage(named: "user-icon-image-placeholder-300-grey.jpg")!
    private enum CodingKeys : String, CodingKey { case message, amount, userId, timestamp }
}

URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
            guard let data = data else { return }
            do {
                // Activities
                let decoder = JSONDecoder()
                decoder.dateDecodingStrategy = .iso8601
                let result = try decoder.decode(Root.self, from: data)
                self.activities = result.activities

                URLSession.shared.dataTask(with: userURL) { (data, response, err) in
                    guard let data = data else { return }
                    do {
                        // Users
                        let usersJson = try JSONSerialization.jsonObject(with: data, options: [])
                        guard let jsonArray = usersJson as? [[String: Any]] else { return }

                        for dic in jsonArray {
                            guard let avatarUrl = dic["avatarUrl"] as? String else { return }
                            print(avatarUrl)
                        }

                    } catch {
                        print("Error serializing json: ", error)
                    }
                    }.resume()
            } catch {
                print("Error serializing json: ", error)
            }

            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }.resume()
tujilar
  • 1
  • 4
  • This looks pretty much like a duplicate of https://stackoverflow.com/questions/56486952/populate-cells-with-json-from-api/56488596#56488596 – vadian Jun 08 '19 at 19:43
  • Yes, I'm the same guy, just forgot the login to that account. Also made some progress, so I figured it would be better to just post another, more specific question. – tujilar Jun 08 '19 at 19:53

1 Answers1

0
  • In the Activity struct add CodingKeys and an extra member for the avatar URL

    struct Activity: Decodable {
        let message: String
        let amount: Float
        let userId: Int
        let timestamp: Date
    
        var avatar : URL = // some default URL to a placeholder image on disk
    
        private enum CodingKeys : String, CodingKey { case message, amount, userId, timestamp }
    } 
    

    The purpose of the CodingKeys is that only the first 4 struct members are decoded.

  • In the completion handler of the first data task add a second data task to load the user data.

  • Decode the JSON as array of dictionaries with JSONSerialization.
  • In a loop get the activity for the given userId and update the avatar URL.
  • Then reload the table view.
  • To load the avatar images you need a logic to download and cache the images asynchronously.
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Thanks for answering, again! 1) How do I pass an image on the assets folder as an URL? I tried `let path = Bundle.main.path(forResource: "user-icon-image-placeholder-300-grey", ofType: "jpg", inDirectory: "Assets.xcassets") var avatar : URL = URL(fileURLWithPath: path!)` but it throws a nil on `path!`, the name of the file is exactly like that, I triple checked. 2) For the `JSONSerialization` part, is it like with the `Decoder`? `let serializer = JSONSerialization()`? If it is, `let userResult = serializer.` doesn't seem right. I think I might be doing it wrong. – tujilar Jun 08 '19 at 21:02
  • Please do some research: There are zillions of questions how to use `JSONSerialization`. No, it’s not like `JSONDecoder`. You can also use an `UIImage` type instead of `URL` and get the image with `UIImage(named` – vadian Jun 08 '19 at 21:13
  • Alrighty. When you say _add a second data task to load the user data_, you mean before `DispatchQueue`? Shouldn't I have to open another `URLSession`? – tujilar Jun 08 '19 at 21:21
  • You have to add another data task on the same shared session – vadian Jun 08 '19 at 21:51
  • Hey, I think I got it, I edited the code in the post, could you please check it and tell me if I'm on the right path? It doesn't enter in the 2nd `if` – tujilar Jun 09 '19 at 01:20
  • Please reread my answer carefully. I mentioned the type of the JSON containing the user data. And I mentioned also not to reload the table view after the first data task. And it's pointless to call the second task if an error occurs in the first. – vadian Jun 09 '19 at 04:23
  • Gotcha, deleted the first reloadData and moved the other one to the end, I think that should do it. I changed the JSON Serialization part and added a `for` loop. How is it looking? Now I get printed all the URLs of the avatars! – tujilar Jun 09 '19 at 13:25
  • Editing the question by applying the solution makes the question incomprehensible for other readers. – vadian Jun 09 '19 at 13:29
  • How about now? I reverted back to the original code and added the updated code at the end. – tujilar Jun 09 '19 at 13:37