I have a UITableViewCell
subclass which has a custom subview which is created through code. Now the problem is I'm trying to make the scrolling of the UITableView less jumpy.
Here's how this is setup.
CustomSubview
is aUIView
created through codeBasePostCell
is aUITableViewCell
is aUITableViewCell
subclass that is used as a base for some other cellsUserPostCell
,TextPostCell
, andDiscussionPostCell
areBasePostCell
subclasses which are made usingxib
s and so far since I don't know if it is possible to somehow inherit an xib to another xib I just usedviewWithTag
andawakeFromNib
to connect the subviews to their respective variables, which you will see on the sample code below- All of these are setup with
NSLayoutConstraints
which from what I've read/researched is significantly slower than if I create the view's through code and then just manually calculate the height, and width of each cell. I would if I could but right now I don't have the luxury of doing so because there are about 20+ different cells in the real code base. (this is just a sample code)
The class I want to change somehow is either CustomSubview
or BasePostCell
; or if there is a better way to do this please tell me.
Here's my code
The Model
class Post {
var type: PostType = .text
var text: String = ""
var title: String = ""
var displayPhoto: String?
// ... there are other attributes here
enum PostType {
case text, user, discussion
}
}
The Base Classes
class CustomSubview: UIView {
lazy var likeButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .black
button.titleLabel?.font = UIFont(face: .helveticaNeue, style: .regular, size: 14) // this is a helper function of mine
button.setTitleColor(UIColor.white, for: .normal)
button.setTitleColor(UIColor.gray, for: .selected)
return button
}()
// The rest are more or less the same as how likeButton is created
// the most important part is `translatesAutoresizingMaskIntoConstraints`
// should be set to true since I use `NSLayoutConstraints`
lazy var commentButton: UIButton = { ... }()
lazy var shareButton: UIButton = { ... }()
lazy var followButton: UIButton = { ... }()
lazy var answerButton: UIButton = { ... }()
func configure(withType type: PostType) {
// I don't know if this is the right way to do this
self.subviews.forEach { $0.removeFromSuperview() }
switch type {
case .text:
[ self.likeButton, self.commentButton, self.shareButton ].forEach { self.addSubview($0) }
// constraints code block
// code goes something like this
self.addConstraints(NSLayoutConstraint.constraints(
withVisualFormat: "H:|-0-[btnLike(==btnComment)]-0-[btnComment]-0-[btnShare(==btnComment)]-0-|",
options: NSLayoutFormatOptions(),
metrics: nil,
views: ["btnLike": self.likeButton,
"btnComment": self.commentButton,
"btnShare": self.shareButton]))
case .user:
[ self.followButton, self.shareButton ].forEach { self.addSubview($0) }
// insert more constraints code block here
case .discussion:
[ self.answerButton, self.commentButton, self.shareButton ].forEach { self.addSubview($0) }
// insert more constraints code block here
}
}
}
class BasePostCell: UITableViewCell {
// ... there are other subviews but
// only this view is modularly created
var customSubview: CustomSubview?
override func awakeFromNib() {
super.awakeFromNib()
self.customSubview = self.viewWithTag(990) as? CustomSubview
}
func configure(withPost post: Post) {
self.customSubview?.configure(withType: post.type)
}
}
The subclasses of the BasePostCell
class UserPostCell: BasePostCell {
var imgDisplayPhoto: UIImageView?
override func awakeFromNib() {
super.awakeFromNib()
self.imgDisplayPhoto = self.viewWithTag(0) as? UIImageView
}
override func configure(withPost post: Post) {
super.configure(withPost: post)
self.imgDisplayPhoto?.image = post.image
}
}
class TextPostCell: BasePostCell {
var lblContent: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
self.lblContent = self.viewWithTag(1) as? UILabel
}
override func configure(withPost post: Post) {
super.configure(withPost: post)
self.lblContent?.text = post.text
}
}
class DiscussionPostCell: BasePostCell {
var lblContent: UILabel?
var lblDiscussionTitle: UILabel?
override func awakeFromNib() {
super.awakeFromNib()
self.lblContent = self.viewWithTag(1) as? UILabel
self.lblDiscussionTitle = self.viewWithTag(2) as? UILabel
}
override func configure(withPost post: Post) {
super.configure(withPost: post)
self.lblContent?.text = post.text
self.lblDiscussionTitle?.text = post.title
}
}
And finally the implementation on a SampleViewController
class SomeViewController: UIViewController {
@IBOutlet var tableView: UITableView!
var posts: [Post] = []
var heightForPost: [IndexPath: CGFloat] = [:]
override func viewDidLoad() {
super.viewDidLoad()
// let's just say I initialized the posts
self.posts = <SomePostsArrayHere>
// ... register nib to tableview codes here.
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}
// ... other implementations
}
// Here is the delegate and dataSource
extension SomeViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = self.posts[indexPath.row]
var postCell: BasePostCell
switch post.type {
case .text:
postCell = tableView.dequeueReusableCell(withIdentifier: "TextPostCell", for: indexPath) as! TextPostCell
case .user:
postCell = tableView.dequeueReusableCell(withIdentifier: "UserPostCell", for: indexPath) as! UserPostCell
case .discussion:
postCell = tableView.dequeueReusableCell(withIdentifier: "DiscussionPostCell", for: indexPath) as! DiscussionPostCell
}
postCell.configure(withPost: post)
return postCell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightForPost[IndexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightForPost[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
}