0

I want to create a like button on a table view cell, just like Instagram, Facebook, and 100s of other social network apps have, but I am struggling to understand how this can be done properly keeping in mind MVC paradigm.

My structure looks like this:

Model - FeedPost class 
View - FeedCell class (UITableViewCell subclass)
Controller - FeedTableViewController class (UITableViewController subclass)

The first thing that came to mind was to do the following:

In FeedCell.swift:

@IBAction func likeButtonPressed(_ sender: AnyObject) {
    if let button = sender as? UIButton {
        post.like(completed: {
            if(completed){
                button.isSelected = !button.isSelected
            }
        })
    }
}

And in FeedPost.class:

func like(completed: (Bool) -> Void ) {
        //Make a request to a server and when it is done call
        completed(true)
    }

But this certainly breaks the MVC pattern, as I access my model from the view. So I probably want to work with my data and view via the view controller. The view controller stores the array of posts. So I want to do the following: - Respond to user pressing the button on the table view cell - Find out which post was liked - Perform the server request passing the id of the post or any other reference to it - Upon successful completion of the request, change button state to selected

How would you do this while following the MVC pattern?

Any examples or open source projects where this was done the right way will be highly appreciated.

Ilya Lapan
  • 1,103
  • 2
  • 12
  • 31

2 Answers2

1

How about something like:

FeedCell.swift:

@IBOutlet var likeButton: UIButton!
var likeButtonPressedHandler: (() -> ())?
var isLikeButtonSelected: Bool {
   get { return likeButton.isSelected }
   set { likeButton.isSelected = newValue }
}
@IBAction func likeButtonPressed(_ button: UIButton) {
    likeButtonPressedHandler?()
}

FeedPost.class:

func like(completion: (Bool) -> Void ) {
    //Make a request to a server and when it is done call
    completion(true)
}

ViewController (UITableViewDataSource):

var posts: [FeedPost]

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier", for: indexPath) as? FeedCell
    let post = posts[indexPath.row]
    cell.isLikeButtonSelected = post.isLiked
    cell.likeButtonPressedHandler = { [weak cell] in
        post.like { completed in
            if let cell = cell where completed {
                cell.isLikeButtonSelected = !cell.isLikeButtonSelected
            }
        })
    }
    return cell
}
Silmaril
  • 4,241
  • 20
  • 22
  • Sorry, not getting it, where do you get a reference to post from? – Ilya Lapan Oct 15 '16 at 20:47
  • There is no reference. I just moved model's update/notify logic from the view to the view controller. Because as you said you *don't want access the model from the view*. – Silmaril Oct 15 '16 at 22:02
  • Yes, but how would I know which post I am accessing? The posts are in an array, so how would I know which array element I need? – Ilya Lapan Oct 15 '16 at 22:03
  • Ah sorry. I misunderstood you. Updated my answer – Silmaril Oct 15 '16 at 22:13
  • Okay, so the idea is to pass a closure into every cell that performs the logic. I mean it works, and I don't think this breaks MVC, but feels kinda weird. Do you have any examples/tutorials/open-source projects where this was done that way? I am just curious as to what are the pros and cons of such an approach. – Ilya Lapan Oct 15 '16 at 22:21
  • Yes, it's the idea. We can do it in two ways with closure or using delegate. But but for me closures here looks better then delegates. – Silmaril Oct 15 '16 at 22:28
  • Don't have a link. But I'll add it later if I can find a good article – Silmaril Oct 15 '16 at 22:29
0

Go with the established Apple pattern of delegation. Instead of handling the button tap in your view, let the like button expose a delegate property with an established protocol. Then have your controller implement that protocol and set the controller as the delegate of each button. Your controller will then access the model and update the view once completed.

Léo Natan
  • 56,823
  • 9
  • 150
  • 195