potter
potter

Reputation: 47

Cannot populate UITableview with json data

I have a local json file ( you can find it here ).

I am trying to populate a UITableView with this json and I can generate the table.

but I can only see the mentioned headers and not the respective data beneath the cells of those headers.

I will post some of the code below and can share more if it helps

enum ProfileViewModelItemType {
    case nameAndPicture
    case about
    case email
    case friend
    case attribute
}

protocol ProfileViewModelItem {
    var type: ProfileViewModelItemType { get }
    var rowCount: Int { get }
    var sectionTitle: String  { get }
}

extension ProfileViewModelItem {
    var rowCount: Int {
        return 1
    }
}

class ProfileViewModelNameItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .nameAndPicture
    }

    var sectionTitle: String {
        return "Main Info"
    } 
}

class ProfileViewModelNameAndPictureItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .nameAndPicture
    }

    var sectionTitle: String {
        return "Main Info"
    }

    var rowCount: Int {
        return 1
    }

    var pictureUrl: String
    var name: String

    init(name: String , pictureUrl: String) {
        self.name = name
        self.pictureUrl = pictureUrl
    }
}

class ProfileViewModelAboutItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .about
    } 

    var sectionTitle: String {
        return "About"
    }
    var rowCount: Int {
        return 1
    }

    var about: String

    init(about: String) {
        self.about = about
    }
}

class ProfileViewModelEmailItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .email
    }

    var sectionTitle: String {
        return "Email"
    }
    var rowCount: Int {
        return 1
    }
    var email: String

    init(email: String) {
        self.email = email
    }
}

class ProfileViewModelAttributeItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .attribute
    }

    var sectionTitle: String {
        return "Attributes"
    }

    var rowCount: Int {
        return attributes.count
    }

    var attributes: [Attribute]

    init(attributes: [Attribute]) {
        self.attributes = attributes
    }
}

class ProfileViewModeFriendsItem: ProfileViewModelItem {
    var type: ProfileViewModelItemType {
        return .friend
    }

    var sectionTitle: String {
        return "Friends"
    }

    var rowCount: Int {
        return friends.count
    }

    var friends: [Friend]

    init(friends: [Friend]) {
        self.friends = friends
    }
}

class ProfileViewModel: NSObject {
    var items = [ProfileViewModelItem]()

    override init() {
        super.init()
        guard let data = dataFromFile("ServerData"), let profile =  Profile(data: data) else {
            return
        }

        if let name = profile.fullName, let pictureUrl = profile.pictureUrl {
            let nameAndPictureItem = ProfileViewModelNameAndPictureItem(name: name, pictureUrl: pictureUrl)
            items.append(nameAndPictureItem)
        }

        if let about = profile.about {
            let aboutItem = ProfileViewModelAboutItem(about: about)
            items.append(aboutItem)
        }

        if let email = profile.email {
            let dobItem = ProfileViewModelEmailItem(email: email)
            items.append(dobItem)
        }

        let attributes = profile.profileAttributes
        // we only need attributes item if attributes not empty
        if !attributes.isEmpty {
             let attributesItem = ProfileViewModelAttributeItem(attributes: attributes)
            items.append(attributesItem)
        }

        let friends = profile.friends
        // we only need friends item if friends not empty
        if !profile.friends.isEmpty {
            let friendsItem = ProfileViewModeFriendsItem(friends: friends)
             items.append(friendsItem)
        }
    }
}

extension ProfileViewModel:UITableViewDataSource {


    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return items[section].sectionTitle
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return items.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items[section].rowCount
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = items[indexPath.section]
        switch item.type {
        case .nameAndPicture:
            if let cell = tableView.dequeueReusableCell(withIdentifier: NameAndPictureCell.identifier, for: indexPath) as? NameAndPictureCell {
                cell.item = item
                return cell
            }
        case .about:
            if let cell = tableView.dequeueReusableCell(withIdentifier: AboutCell.identifier, for: indexPath) as? AboutCell {
                cell.item = item
               return cell
            }
        case .email:
            if let cell = tableView.dequeueReusableCell(withIdentifier: EmailCell.identifier, for: indexPath) as? EmailCell {
                cell.item = item
                return cell
            }
        case .friend:
            if let item = item as? ProfileViewModeFriendsItem, let cell = tableView.dequeueReusableCell(withIdentifier: FriendCell.identifier, for: indexPath) as? FriendCell {
                let friend = item.friends[indexPath.row]
                cell.item = friend
                return cell
            }
        case .attribute:
            if let item = item as? ProfileViewModelAttributeItem, let cell = tableView.dequeueReusableCell(withIdentifier: AttributesCell.identifier, for: indexPath) as? AttributesCell {
                cell.item = item.attributes[indexPath.row]
               return cell
            }
        }

        // return the default cell if none of above succeed
        return UITableViewCell()
    }
}


public func dataFromFile(_ filename: String) -> Data? {
    @objc class TestClass: NSObject { }

    let bundle = Bundle(for: TestClass.self)
    if let path = bundle.path(forResource: filename, ofType: "json") {
        return (try? Data(contentsOf: URL(fileURLWithPath: path)))
    }
    return nil
}

class Profile {
    var fullName: String?
    var pictureUrl: String?
    var email: String?
    var about: String?
    var friends = [Friend]()
    var profileAttributes = [Attribute]()

    init?(data: Data) {
        do {
             if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
                let body = json["data"] as? [String: Any] {
                self.fullName = body["fullName"] as? String
                self.pictureUrl = body["pictureUrl"] as? String
                self.about = body["about"] as? String
                self.email = body["email"] as? String

                if let friends = body["friends"] as? [[String: Any]] {
                    self.friends = friends.map { Friend(json: $0) }
                }

                if let profileAttributes = body["profileAttributes"] as? [[String: Any]] {
                    self.profileAttributes = profileAttributes.map { Attribute(json: $0) }
                }
            }
        } catch {
            print("Error deserializing JSON: \(error)")
            return nil
        }
    }
}

class Friend {
    var name: String?
    var pictureUrl: String?

    init(json: [String: Any]) {
        self.name = json["name"] as? String
        self.pictureUrl = json["pictureUrl"] as? String
    }
}

class Attribute {
    var key: String?
    var value: String?
    init(json: [String: Any]) {
        self.key = json["key"] as? String
        self.value = json["value"] as? String
    }
 }

the table view should not only have names of friends but the images also which are present in the assets .

I have also defined custom classes for all the different types of cell and dequeued them and registered them

been looking at it for a week now but still no, any help would be appreciated

I have also provided the frame and added all labels, images etc to the table view as subView but no luck

Upvotes: 0

Views: 80

Answers (1)

horseshoe7
horseshoe7

Reputation: 2817

I had a bit of difficulty understanding. First, "i have also provided the frame and added all label, images etc to the table view as subview" sounds wrong. You should never need to call addSubview(...) on a UITableView. You add UI elements to a UITableViewCell via tableCell.contentView.addSubview(...) but even then, you're best to design your table view cells in interface builder using autolayout. Setting frames is almost useless nowadays now that autolayout is here for good. The only place to set actual frames is in layoutSubviews()

Secondly, is it not displaying because you have your rowHeight to be a constant instead of UITableView.automaticDimension? Maybe you have cells designed to be a certain height, but you haven't told the table view that they have that height? I'd suggest googling "self-sizing table cells with autolayout" or using the table view delegate method ...heightForRowAt indexPath: ...

And finally, looking at your code, I would suggest modernizing your approach to JSON parsing:

Something like this, or just google "JSON and Codable swift"

Upvotes: 2

Related Questions