2
\$\begingroup\$

I have two view controllers which have same tableview. One is for Post and one is for Profile, which also contains a post by a user. I have decided to reuse the code, so I have created the class PostTableView.

class PostTableView: UITableView {

    var arrayPosts:[PostTableViewCompitible] = [] {
        didSet {
            self.reloadData()
        }
    }
    var btnCommentTapped:((ArrayInspireListCell) -> ())?
    var heightOfRows = Dictionary<IndexPath, CGFloat>()
    var indexOfCellBeforeDragging = 0


    override func awakeFromNib() {
        super.awakeFromNib()
        setupTableView()
    }

    private func setupTableView() {
        self.register(UINib.init(nibName: StoryBoard.Cells.ArrayInspireListCell, bundle: nil), forCellReuseIdentifier: StoryBoard.Cells.ArrayInspireListCell)
        self.dataSource = self
        self.delegate = self
        self.estimatedRowHeight = 470
        //    self.tableView.decelerationRate = UIScrollViewDecelerationRateFast
        self.rowHeight = UITableViewAutomaticDimension
        self.separatorStyle = .none


    }

}

With Delegate / Data source:

extension PostTableView:UITableViewDataSource,UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.arrayPosts.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let post = self.arrayPosts[indexPath.row]
        let cell = post.cellForTableView(tableview: tableView, atIndexPath: indexPath)
        cell.setCollectionViewDataSourceDelegate(dataSourceDelegate: self, forRow: indexPath.row)
        cell.layer.shouldRasterize = true
        cell.layer.rasterizationScale = UIScreen.main.scale

        cell.btnCommentTapped = self.btnCommentTapped

        cell.layoutIfNeeded()

        return cell
    }

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        if let height = self.heightOfRows[indexPath] {
            return height
        }
        return 470
    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let height = cell.frame.size.height
        self.heightOfRows[indexPath] = height + 30
    }


}

Is it good practice to do so?

\$\endgroup\$
0
\$\begingroup\$

Personally I don't like the way UITableViews work with a delegate and dataSource yet there's no real way to say "Here's the data". You are kind of forced to either send around copies of your data or put everything into one class. There's tons of mixing of the M, V and C layers in one class going on.

However, it should be possible to use a separate implementation of the dataSource and `delegate. While in this example there's little code, in some cases you might cross (my personal) soft limit of 200 lines of code or even the hard limit of 300 in one file.

In my Objective-C / Storyboards days I actually had implementations where I would use several objects all linked together via @IBOutlets. So a delegate, dataSource and tableView. The delegate could have a weak ref to the dataSource and the dataSource had the actual data. Not using the selected mechanism for button-like actions could allow you to sever the ties between the delegate and the data.

So I don't disapprove of what you're doing because you're trying to do your best with the bad hand of cards that Apple dealt you. But you could try to have the data only exist on the dataSource implementation. Also I see you doing a lot of stuff to the cell in dequeue, that should definitely move to the implementation of the cell because it's doing V code in an M class. I use a configure(with: *someData*) function that sets it up further.

If you try to separate the M from the V and the C you will see pretty clearly where you should cut it up.

\$\endgroup\$
0
\$\begingroup\$

This could be a good idea if you have 2 or more instances of UITableView with the same appearance in your app. Anyway it is better than to copy nearly the same code for each class that conforms to UITableViewDataSource and UITableViewDelegate. I just want to advice you to add the following computed properties to PostTableView:

class PostTableView: UITableView {

    // Other properties

    var cellIdentifier: String {
        return StoryBoard.Cells.ArrayInspireListCell
    }

    var defaultRowHeight: CGFloat {
        return 470.0
    }

    var additionalItemHeight: CGFloat {
        return 30.0
    }
}

They are overridable because PostTableView was not declared as final.

As you can see there are less magic numbers:

private func setupTableView() {
    self.register(UINib(nibName: cellIdentifier, bundle: nil), forCellReuseIdentifier: cellIdentifier)
    self.estimatedRowHeight = defaultRowHeight
    // Initialisation
}

You can also use these properties in the implementation of other functions:

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    if let height = self.heightOfRows[indexPath] {
        return height
    }
    return defaultRowHeight
}

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let height = cell.frame.size.height
    self.heightOfRows[indexPath] = height + additionalItemHeight
}

I don't know how do you set the value of PostTableView.arrayPosts, but maybe it will be better to ensure that UITableView.reloadData will be called in the main thread:

var arrayPosts: [PostTableViewCompitible] = [] {
    didSet {
        DispatchQueue.main.async { [unowned self] in
            self.reloadData()
        }
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.