Swift UITableView separator hidden until scroll - ios

I have implemented a custom table view cell which appears but without the separator until you scroll. In the viewDidAppear I have set the separator style, the label border is not overlapping the cell edges. Help
Before Scrolling
After Scrolling
Larger Sim Window
On device testing
The pattern is MVVM with a custom cell.
Model
import Foundation
struct OAuthList {
let providers: [String]
init() {
self.providers = OAuthProviders.providers
}
}
View Model
import Foundation
struct OAuthListViewModel {
var providerList: [String]
init(providers: [String]) {
self.providerList = providers
}
}
LoginViewController
import UIKit
class LoginViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
var providerButtons = OAuthListViewModel(providers: OAuthProviders.providers)
override func viewDidLoad() {
super.viewDidLoad()
let attributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 17)]
self.navigationController?.navigationBar.titleTextAttributes = attributes
self.navigationController?.navigationBar.isTranslucent = false
self.navigationController?.navigationBar.barTintColor = #colorLiteral(red: 1, green: 0.738589704, blue: 0.9438112974, alpha: 1)
self.navigationItem.title = "LOGIN / SIGNUP"
self.navigationItem.leftBarButtonItem?.tintColor = .white
self.navigationItem.leftBarButtonItem?.isEnabled = false
self.tableView.separatorColor = .white
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.register(CustomCell.self, forCellReuseIdentifier: TextCellIdentifier.textCellIdentifier)
self.tableView.layoutMargins = UIEdgeInsets.zero
self.tableView.separatorInset = UIEdgeInsets.zero
self.tableView.tableFooterView = UIView()
}
}
extension LoginViewController {
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return providerButtons.providerList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TextCellIdentifier.textCellIdentifier, for: indexPath) as! CustomCell
let row = indexPath.row
cell.backgroundColor = #colorLiteral(red: 1, green: 0.738589704, blue: 0.9438112974, alpha: 1)
cell.buttonLabel.text = providerButtons.providerList[row]
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(providerButtons.providerList[indexPath.row])
}
}
Custom Cell
class CustomCell: UITableViewCell {
var labelText: String?
var buttonLabel: UILabel = {
var label = UILabel()
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: TextCellIdentifier.textCellIdentifier)
self.addSubview(buttonLabel)
buttonLabel.translatesAutoresizingMaskIntoConstraints = false
buttonLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
buttonLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
buttonLabel.textColor = UIColor.white
}
override func layoutSubviews() {
if let labelText = labelText {
buttonLabel.text = labelText
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

You can hide the separator and simply add UIView to act as a separator inside your custom cells

Enlarge your simulator window or try it on an actual device not sim.
When the sim window is small enough it can have a hard time displaying separators in a tableview since the separators are normally only 0.5pt in height.
A good indicator that the issue I mentioned above is occurring is when you scroll the tableview on the simulator and random separators start to appear while some are hidden. Which is what appears to be happening in your second screenshot.

Related

UITableView CustomCell Reuse (ImageView in CustomCell)

I'm pretty new to iOS dev and I have an issue with UITableViewCell.
I guess it is related to dequeuing reusable cell.
I added an UIImageView to my custom table view cell and also added a tap gesture to make like/unlike function (image changes from an empty heart(unlike) to a filled heart(like) as tapped and reverse). The problem is when I scroll down, some of the cells are automatically tapped. I found out why this is happening, but still don't know how to fix it appropriately.
Below are my codes,
ViewController
import UIKit
struct CellData {
var title: String
var done: Bool
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = models[indexPath.row]
guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as? TableViewCell else {
return UITableViewCell()
}
cell.textLabel?.text = model.title
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
}
}
TableViewCell
import UIKit
class TableViewCell: UITableViewCell {
let mainVC = ViewController()
static let identifier = "TableViewCell"
let likeImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(likeImage)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImage.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
likeImage.widthAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.heightAnchor.constraint(equalToConstant: 30).isActive = true
likeImage.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
likeImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
if likeImage.image == UIImage(systemName: "heart.fill"){
likeImage.image = UIImage(systemName: "heart")
likeImage.tintColor = .darkGray
} else {
likeImage.image = UIImage(systemName: "heart.fill")
likeImage.tintColor = .systemRed
}
}
}
This gif shows how it works now.
enter image description here
I've tried to use "done" property in CellData structure to capture the status of the uiimageview but failed (didn't know how to use that in the correct way).
I would be so happy if anyone can help this!
You've already figured out that the problem is cell reuse.
When you dequeue a cell to be shown, you are already setting the cell label's text based on your data:
cell.textLabel?.text = model.title
you also need to tell the cell whether to show the empty or filled heart image.
And, when the user taps that image, your cell needs to tell the controller to update the .done property of your data model.
That can be done with either a protocol/delegate pattern or, more commonly (particularly with Swift), using a closure.
Here's a quick modification of the code you posted... the comments should give you a good idea of what's going on:
struct CellData {
var title: String
var done: Bool
}
class ShinViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var models = [CellData]()
private let tableView: UITableView = {
let table = UITableView()
table.register(ShinTableViewCell.self, forCellReuseIdentifier: ShinTableViewCell.identifier)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.delegate = self
tableView.dataSource = self
configure()
}
private func configure() {
self.models = Array(0...50).compactMap({
CellData(title: "\($0)", done: false)
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ShinTableViewCell.identifier, for: indexPath) as! ShinTableViewCell
let model = models[indexPath.row]
cell.myLabel.text = model.title
// set the "heart" to true/false
cell.isLiked = model.done
// closure
cell.callback = { [weak self] theCell, isLiked in
guard let self = self,
let pth = self.tableView.indexPath(for: theCell)
else { return }
// update our data
self.models[pth.row].done = isLiked
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
class ShinTableViewCell: UITableViewCell {
// we'll use this closure to communicate with the controller
var callback: ((UITableViewCell, Bool) -> ())?
static let identifier = "TableViewCell"
let likeImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(systemName: "heart")
imageView.tintColor = .darkGray
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
let myLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// we'll load the heart images once in init
// instead of loading them every time they change
var likeIMG: UIImage!
var unlikeIMG: UIImage!
var isLiked: Bool = false {
didSet {
// update the image in the image view
likeImageView.image = isLiked ? likeIMG : unlikeIMG
// update the tint
likeImageView.tintColor = isLiked ? .systemRed : .darkGray
}
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// make sure we load the heart images
guard let img1 = UIImage(systemName: "heart"),
let img2 = UIImage(systemName: "heart.fill")
else {
fatalError("Could not load the heart images!!!")
}
unlikeIMG = img1
likeIMG = img2
// add label and image view
contentView.addSubview(myLabel)
contentView.addSubview(likeImageView)
layout()
//Tap Gesture Recognizer 실행하기
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(_:)))
likeImageView.addGestureRecognizer(tapGestureRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
}
override func prepareForReuse() {
super.prepareForReuse()
}
private func layout() {
// let's use the "built-in" margins guide
let g = contentView.layoutMarginsGuide
// image view bottom constraint
let bottomConstraint = likeImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor)
// this will avoid auto-layout complaints
bottomConstraint.priority = .required - 1
NSLayoutConstraint.activate([
// constrain label leading
myLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
// center the label vertically
myLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
// constrain image view trailing
likeImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// constrain image view to 30 x 30
likeImageView.widthAnchor.constraint(equalToConstant: 30),
likeImageView.heightAnchor.constraint(equalTo: likeImageView.widthAnchor),
// constrain image view top
likeImageView.topAnchor.constraint(equalTo: g.topAnchor),
// activate image view bottom constraint
bottomConstraint,
])
}
#objc func didTapImageView(_ sender: UITapGestureRecognizer) {
// toggle isLiked (true/false)
isLiked.toggle()
// inform the controller, so it can update the data
callback?(self, isLiked)
}
}

Programmatically Creating TableViewControllers inside TabBarController will not render labels

I want to programmatically create a tabBarView with two tableViewControllers
In my AppDelegate.swift file I added the following at the top:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
let mainVC = MainTabBarController()
window?.rootViewController = mainVC
return true
}
I have my MainTabBarController.swift file with the following:
import Foundation
import UIKit
class MainTabBarController : UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setUpTabBar()
}
func setUpTabBar() {
let firstTableViewController = UINavigationController(rootViewController: FirstTableViewController())
firstTableViewController.tabBarItem.image = UIImage(named: "first")
firstViewController.tabBarItem.selectedImage = UIImage(named: "first-selected")
let secondViewController = UINavigationController(rootViewController: SecondTableViewController())
SecondTableViewController.tabBarItem.image = UIImage(named: "second")
SecondTableViewController.tabBarItem.selectedImage = UIImage(named: "second-selected")
viewControllers = [firstViewController, SecondViewController]
guard let items = tabBar.items else { return }
for item in items {
item.imageInsets = UIEdgeInsets(top: 4, left: 0, bottom: -4, right: 0)
}
}
}
I have my FirstTableViewController.swift with the following:
import Foundation
import UIKit
class FirstTableViewController : UITableViewController {
let reuseIdentifier = "firstId"
override func viewDidLoad() {
setUpNavigationBar()
setUpTableView()
}
private func setUpTableView() {
self.tableView.register(FirstTableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
}
func setUpNavigationBar() {
navigationItem.title = "First"
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.darkGray, NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20)]
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! FirstTableViewCell
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
Here is FirstTableViewCell.swift:
import Foundation
import UIKit
class FirstTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUp()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
let cellView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
view.setCellShadow()
return view
}()
let firstItemLabel: UILabel = {
let label = UILabel()
label.text = "Name"
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 16)
return label
}()
func setUp() {
addSubview(cellView)
cellView.addSubview(firstItemLabel)
}
}
With this current view of FirsTableView, all I see is an empty tableView with no data populated even though I should be seeing "Name" inside every cell: https://imgur.com/a/Ecenn3O.
So I tried something different with SecondTableViewController.swift:
import Foundation
import UIKit
class SecondTableViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
let reUseIdentifier: String = "secondId"
let tableView : UITableView = {
let tb = UITableView()
tb.separatorStyle = .none
tb.allowsSelection = false
return tb
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpNavigationBar()
setUpTableView()
}
func setUpNavigationBar() {
navigationItem.title = "Second"
navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.darkGray, NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 20)]
}
func setUpTableView() {
tableView.delegate = self
tableView.dataSource = self
tableView.register(SecondTableViewCell.self, forCellReuseIdentifier: reUseIdentifier)
view.addSubview(tableView)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reUseIdentifier, for: indexPath) as! SecondTableViewCell
return cell
}
}
Where SecondTableViewCell.swift is very similar to FirstTableViewCell.swift:
import UIKit
class SecondTableViewCell: UITableViewCell {
let cellView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.white
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUp()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUp() {
addSubview(cellView)
cellView.addSubview(secondTableViewCellLabel)
}
let secondTableViewCellLabel: UILabel = {
let label = UILabel()
label.text = "Name"
label.textColor = .darkGray
label.font = UIFont.boldSystemFont(ofSize: 16)
return label
}()
}
This renders nothing but a black screen with the tab name up above: https://imgur.com/veTNln6.
I'm new to creating views programmatically and I have no idea what else needs to be done to have the tabBar display both tableViews as intended.
I followed these two videos as a reference:
UITabBar programmatically
UITableView programmatically
Any help would be greatly appreciated.

SnapKit - UITableView height is not change with dynamical content of cell

I'm a beginner in SnapKit, I want to implement a UITableViewController with SnapKit, each row have two UILabel, one of them is Title and another one is Value.
My issue is the height of the row in UITableView not changed according to the content of each row.
here is my code:
class ViewController:
import UIKit
import SnapKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: Property List
var list: NSMutableArray?
let myTableView: UITableView = {
let table = UITableView()
return table
}()
//MARK: Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.myTableView.rowHeight = UITableView.automaticDimension
self.myTableView.estimatedRowHeight = 100
title = "TableView Page"
setup()
setupViews()
}
// MAKR: Setup View
func setup() {
self.view.backgroundColor = .white
navigationController?.navigationBar.prefersLargeTitles = true
}
func setupViews () {
self.view.addSubview(myTableView)
myTableView.snp.makeConstraints { make in
make.top.left.bottom.right.equalTo(10)
}
myTableView.register(CustomCell.self, forCellReuseIdentifier: CustomCell.customCell)
myTableView.delegate = self
myTableView.dataSource = self
}
// MARK: TableView DataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CustomCell.customCell, for: indexPath) as! CustomCell
cell.title.text = "title is lognest??"
cell.value.text = (indexPath.row % 2 == 0) ?
"longest value longest value longest value longest value longest value longest value longest value for text"
: "short value"
cell.title.snp.makeConstraints { (make) in
make.top.equalTo(cell.value.snp.top)
make.left.equalTo(20)
make.trailing.equalTo(cell.value.snp.leading)
}
cell.value.snp.makeConstraints { (make) in
make.right.equalTo(-20)
make.top.equalTo(cell.title.snp.top)
make.bottom.equalTo(-10)
}
NSLog("value height is: \(cell.value.frame.height)")
NSLog("cell height is: \(cell.frame.height)")
return cell;
}
}
class CustomCell:
// MARK: custom cell
class CustomCell: UITableViewCell {
// MARK: Property
static var customCell = "cell"
public var title:UILabel = {
let tit = UILabel()
tit.setContentHuggingPriority(.defaultLow, for: .horizontal)
tit.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
tit.textColor = .black
tit.alpha = 0.6
return tit
}()
public var value:UILabel = {
let val = UILabel()
val.textColor = .black
val.setContentHuggingPriority(.defaultHigh, for: .horizontal)
val.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
val.lineBreakMode = NSLineBreakMode.byWordWrapping
val.numberOfLines = 0
val.alpha = 0.75
return val
}()
// MARK: initializer
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(title)
self.addSubview(value)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init?(coder aDecoder: NSCoder)")
}
}
And this is console logs:
snapkitTest[26345:1387159] cell height is: 44.0
snapkitTest[26345:1387159] value height is: 0.0
1- You have to add the label to
self.contentView.addSubview(title)
2- You need to set bottom constraint to contentView
title.snp.makeConstraints { (make) -> Void in
make.trailing.equalTo(value.snp.leading)
make.top.equalTo(self.contentView.snp.top).inset(10)
make.left.equalTo(20)
}
value.snp.makeConstraints { (make) -> Void in
make.right.equalTo(-40)
make.top.equalTo(self.contentView.snp.top).inset(10)
make.bottom.equalTo(-10)
}
Also transfer this constraints to init of cell custom class , as not to re-add constraints every scroll of tableView

Get section header cell in gesture method

I am working on a 'UITableView' with different section headers. Section header contains a tab gesture recognization to expand and collapse the section.
In the section header view, I have used an image for the accessory icon to show the user the section is expanded or collapsed.
My concern is when I tap section header then control goes to the gesture method. In that method how should I get the header cell to update the image accordingly?
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
if self.useSearchDefinitions {
if let ret = tableView.dequeueReusableCell(withIdentifier: INBOX_HEADER_CELL_IDENTIFIER) as? InboxHeaderCell {
ret.contentView.backgroundColor = UIColor(red: 236 / 255.0, green: 236 / 255.0, blue: 236 / 255.0, alpha: 1.0)
ret.contentView.tag = section
ret.lblHeaderTitle?.textColor = UIColor(red: 110 / 255.0, green: 110 / 255.0, blue: 110 / 255.0, alpha: 1.0)
ret.lblHeaderTitle?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
ret.lblHeaderTitle?.text = presenter.sectionTitle(section)
ret.accessoryImage.image = UIImage(named: "inbox-expand.png")
// Set tap gesture
let headerViewTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.headerViewGestureHandler))
headerViewTapRecognizer.delegate = self
headerViewTapRecognizer.numberOfTouchesRequired = 1
headerViewTapRecognizer.numberOfTapsRequired = 1
ret.contentView.addGestureRecognizer(headerViewTapRecognizer)
return ret.contentView
}
}
return nil
}
and this is to get the gesture
func headerViewGestureHandler(_ sender: UIGestureRecognizer)
{
tableView.beginUpdates()
if let tag = sender.view?.tag {
let section = Int(tag)
let shouldCollapse: Bool = !collapsedSections.contains((section))
let numOfRows = Int(presenter.numberOfRows(tag))
}
}
how should I get the particular clicked section header cell in this method so I can update the image accordingly?
Thanks in advance.
I would recommend:
put the Gesture code inside your section header
using a "call back" closure for passing the tap back to the view controller
Here is a simple example (assumes you have a View Controller with a Table View, hooked up via IBOutlet):
class SimpleSectionHeaderView: UITableViewHeaderFooterView, UIGestureRecognizerDelegate {
// typical UILabel
var lblHeaderTitle: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
// this is our "call back" closure
var headerTapCallback: (() -> ())?
func headerViewGestureHandler(_ sender: UIGestureRecognizer) {
// just for debugging, so we know the tap was triggered
print("tapped!!!")
// "call back" to the view controller
headerTapCallback?()
}
func commonInit() {
// set our backgroundColor
contentView.backgroundColor = .cyan
// add a label and set its constraints
self.addSubview(lblHeaderTitle)
lblHeaderTitle.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8.0).isActive = true
lblHeaderTitle.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0.0).isActive = true
// Set tap gesture
let headerViewTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.headerViewGestureHandler))
headerViewTapRecognizer.delegate = self
headerViewTapRecognizer.numberOfTouchesRequired = 1
headerViewTapRecognizer.numberOfTapsRequired = 1
// add it to self
self.addGestureRecognizer(headerViewTapRecognizer)
}
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
}
class TableWithSectionHeadersViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var theTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// standard cell registration
theTableView.register(UITableViewCell.self, forCellReuseIdentifier: "reuseIdentifier")
theTableView.register(SimpleSectionHeaderView.self, forHeaderFooterViewReuseIdentifier: "simpleHeaderView")
// make sure these are set (in case we forgot in storyboard)
theTableView.delegate = self
theTableView.dataSource = self
}
func handleHeaderTap(_ section: Int) -> Void {
// do whatever we want based on which section header was tapped
print("View Controller received a \"tapped\" in header for section:", section)
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let v = tableView.dequeueReusableHeaderFooterView(withIdentifier: "simpleHeaderView") as! SimpleSectionHeaderView
// set the view's label text
v.lblHeaderTitle.text = "Section \(section)"
// set the view's "call back" closure
v.headerTapCallback = {
_ in
self.handleHeaderTap(section)
}
return v
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60;
}
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath)
// Configure the cell...
cell.textLabel?.text = "\(indexPath)"
return cell
}
}
This also eliminates any need to set any .tag properties (which is generally a bad idea, for a number of reasons).

How can custom tableview cells be made in swift playground?

In swift playground (not in a project)
Could you please give me a hint about how to do this please?enter image description here
You have to implement init(style:reuseIdentifier:) in your custom class to define subviews and constraints programmatically.
Thus:
import UIKit
import PlaygroundSupport
class CustomCell: UITableViewCell {
weak var label1: UILabel!
weak var label2: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
let label1 = UILabel()
label1.translatesAutoresizingMaskIntoConstraints = false
label1.font = .preferredFont(forTextStyle: .title1)
label1.backgroundColor = #colorLiteral(red: 0.572549045085907, green: 0.0, blue: 0.23137255012989, alpha: 1.0)
label1.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contentView.addSubview(label1)
self.label1 = label1
let label2 = UILabel()
label2.translatesAutoresizingMaskIntoConstraints = false
label2.font = .preferredFont(forTextStyle: .title1)
label2.backgroundColor = #colorLiteral(red: 0.0901960805058479, green: 0.0, blue: 0.301960796117783, alpha: 1.0)
label2.textColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
contentView.addSubview(label2)
self.label2 = label2
NSLayoutConstraint.activate([
label1.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
label2.leadingAnchor.constraint(equalTo: label1.trailingAnchor, constant: 10),
contentView.trailingAnchor.constraint(equalTo: label2.trailingAnchor, constant: 10),
label1.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
label2.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: label1.bottomAnchor, constant: 10),
contentView.bottomAnchor.constraint(equalTo: label2.bottomAnchor, constant: 10),
label1.widthAnchor.constraint(equalTo: label2.widthAnchor)
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And then you'd have a standard UITableViewController which registers this class and uses it:
class ViewController: UITableViewController {
var objects: [String] = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return Array(0 ..< 1000).flatMap { formatter.string(from: NSNumber(value: $0)) }
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
// set it up to let auto-layout resize the cell
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.label1.text = objects[indexPath.row]
cell.label2.text = "Row \(indexPath.row)"
return cell
}
}
PlaygroundPage.current.liveView = ViewController()
Now that just creates a simple view like this:
That's not terribly pretty, but I set the background colors like I did, just so you can see that the frames are set appropriately. You'd obviously adjust your init(style:reuseIdentifier:) to create subviews more to your liking.
If you want the standard cell, then you don't need to have a custom cell subclass, but you can just use UITableViewCell:
import UIKit
import PlaygroundSupport
class ViewController: UITableViewController {
var objects: [String] = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return Array(0 ..< 1000).flatMap { formatter.string(from: NSNumber(value: $0)) }
}()
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = objects[indexPath.row]
return cell
}
}
PlaygroundPage.current.liveView = ViewController()
On the iPad, that yields:
On Playground in Xcode, it yields:
Full credit to #Rob, this is a tweak of his answer, but after trying to directly add the cell to a table view to no avail (cells seem to ignore explicit sizing), I reduced his view controller to a generic one for ease of re-use.
class DashboardViewController<T: UITableViewCell>: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(T.self, forCellReuseIdentifier: "Cell")
// set it up to let auto-layout resize the cell
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! T
configure(cell)
return cell
}
var configure: ((T) -> ())!
static func with(configuration: ((T) -> ())!) {
let controller = DashboardViewController<T>()
controller.configure = configuration
PlaygroundPage.current.liveView = controller
}
}
// usage:
DashboardViewController<MyCustomCell>.with() { cell in
let model = MyCellModel()
cell.model = model }

Resources