byaruhaf
byaruhaf

Reputation: 4733

Using SwiftUI's For Each to determine which View to Add to the List

I'm trying to recreate the UIKit below in SwiftUI using ForEach

func configureCell(for post: MediaPost, in tableview: UITableView) -> UITableViewCell {
    if let post = post as? TextPost {
        let cell = tableview.dequeueReusableCell(withIdentifier: CellType.text) as! TextPostCell
        return cell
    } else{
        guard let post = post as? ImagePost  else { fatalError("Unknown Cell") }
        return cell
    }
}

Here is my model

protocol PostAble {
    var id:UUID { get }
}

struct MediaPost: PostAble,Identifiable {
    let id = UUID()
    let textBody: String?
    let userName: String
    let timestamp: Date
    let uiImage: UIImage?
}


struct RetweetMediaPost: PostAble,Identifiable {
    let id = UUID()
    let userName: String
    let timestamp: Date
    let post: MediaPost
}

So I have created an array in ViewModel

class PostViewModel: ObservableObject {
  @Published var posts: [PostAble] = []
}

and I would like to iterate of this array with a ForEach and build up a list of views. Here is the code I wrote up

struct PostListView: View {
    @ObservedObject var postViewModel = PostViewModel()
    var body: some View {
        List {
            ForEach(postViewModel.posts, id: \.id) { post in
                if let post = post as? MediaPost {
                    PostView(post: post)
                } else {
                    guard let post = post as Retweet else { fatalError("Unknown Type") }
                    RetweetView(post: post)
                }

            }
        }
    }
}

Which gives me this error

Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols

I understand the error and I know why it's failing but don't have another solution to rewrite. Can this be achieved with swiftUI?

Upvotes: 1

Views: 702

Answers (1)

Asperi
Asperi

Reputation: 257711

Try the following. Tested with Xcode 11.4 / iOS 13.4.

Note: you created view model inside view, so make sure you fill it also in view, eg. in .onAppear, otherwise just declare to provide it externally

struct PostListView: View {
    @ObservedObject var postViewModel = PostViewModel()
    // @ObservedObject var postViewModel: PostViewModel  // for external !!

    var body: some View {
        List {
            ForEach(postViewModel.posts, id: \.id) { post in
                self.view(for: post)
            }
        }
    }

    private func view(for post: PostAble) -> some View {
        let mediapost = post as? MediaPost
        let retweet = post as? RetweetMediaPost
        return Group {
            if mediapost != nil {
                PostView(post: mediapost!)
            }
            if retweet != nil {
                RetweetView(post: retweet!)
            }
        }
    }
}

Upvotes: 1

Related Questions