Search code examples
arraysswiftswiftuimodel

Combining 2 Models based on its properties to create another Model in Swift


Disclaimer: English is not my native tongue, but I'll try my best to explain the problem.

In this image I have 2 Lists, top displays a list of PostModels and bottom a list of UserModels.

Both of which are taken from an API, I am trying to create a list where PostModels userId == UserModels id.

Expected Output: Is something like what you would see in a facebook post. Where you will see the user and text in the ListItem.

How do I achieve this?

Models

struct PostModel {
    let id: Int
    let userId: Int
    let title: String
    let body: String
}
struct UserModel {
    let id: Int
    let firstName: String
    let lastName: String
    let email: String
    let image: String
}

//The 3rd model where I will store data taken from Post and User
struct PostWithUser {
    let postUserId: Int
    let userImg: String
    let postUserName: String
    let postTitle: String
    let postBody: String
}

ViewModel

class DashboardViewModel: WebViewModel, ObservableObject {
    var manager: UserManager = UserManager()
    var postManager: PostManager = PostManager()
    
    @Published var users: UsersModel = UsersModel(users: [UserModel(id: 0, firstName: "", lastName: "", email: "", image: "")])
    @Published var posts: PostsModel = PostsModel(posts: [PostModel(id: 0,userId: 0, title: "", body: "")])
    
    @Published var postsWithUser = [PostWithUser(postUserId: 0, userImg: "",postUserName: "", postTitle: "", postBody: "")]
    
    func getUsers() {
        manager.getUsers { [weak self] result in
            switch result {
            case .success(let usersModel):
                DispatchQueue.main.async {
                    self?.users = usersModel
                }
            case .failure(let error):
                print("Error: \(error)")
            }
        }
    }
    
    func getPosts() {
        postManager.getPosts { [weak self] result in
            switch result {
            case .success(let postsModel):
                DispatchQueue.main.async {
                    self?.posts = postsModel
                }
            case .failure(let error):
                print(error)
            }
        }
    }
    
    func getPostsWithUser() {
        for post in self.posts.posts {
            for user in self.users.users {
                if user.id == post.userId {
                    let post = PostWithUser(postUserId: post.userId, userImg: user.image, postUserName: "\(user.firstName) \(user.lastName)", postTitle: post.title, postBody: post.body)
                    
                    self.postsWithUser.append(post)
                }
            }
        }
    }
}

View

struct DashboardView: View {
    @StateObject private var viewModel = DashboardViewModel()
    
    var body: some View {
        let users = viewModel.users.users
        let posts = viewModel.posts.posts
        let detailedPosts = viewModel.postsWithUser
        
        VStack {
            Text("Posts")
            List {
                ForEach(detailedPosts) { post in
                    Text(post.postTitle)
                }
            }.task {
                viewModel.getPosts()
                viewModel.getUsers()
                viewModel.getPostsWithUser()
            } 
        }
    }
}


Solution

  • One solution is to look up the user using a function rather than creating a new type.

    class DashboardViewModel: ObservableObject {
    
        var users: [UserModel] = [UserModel(id: 0, firstName: "", lastName: "", email: "", image: "")]
        @Published var posts: PostsModel = PostsModel(posts: [PostModel(id: 0,userId: 0, title: "", body: "")])
        
    
        func userWith(id: Int) -> UserModel {
            users.first(where: { $0.id == id}) ?? .unknown
        }
    
        //...
    }
    

    Where I have created an unknown user in an extension to UserModel

    extension UserModel {
        static let unknown = UserModel(id: -1, firstName: "", lastName: "Unknown", email: "", image: "")
    }
    

    And then use it in the view like

    ForEach(detailedPosts) { post in
        Text(post.postTitle)
        Text(viewModel.userWith(id: post.userId).email)
    }
                
    

    Note that since I don't know what UsersModel looks like I changed it to an array in my code.