How to apply custom styling to NSTableHeaderView? - swift

So I am going for a custom looking NSTableView. I've already successfully subclassed NSTableRowView and NSTextFieldCell to achieve the look I'm going for, however I'm struggling of getting rid of the default styling for the header. I seem to be able to tweak its frame, however I'm not sure where the rest of the default styling is coming from.
As you see on the screenshot the red area is the increased frame of the headerView. I'm using its CALayer to set the colour, however how to change the contents inside is beyond me...
Here's what I'm doing in the viewDidLoad of my ViewController
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.wantsLayer = true
tableView.headerView?.frame = NSMakeRect(0, 0, (tableView.headerView?.frame.width)!, 32.00)
tableView.headerView?.wantsLayer = true
tableView.headerView?.layer?.backgroundColor = NSColor.red.cgColor
}
I've also tried subclassing NSTableHeaderView, however this class seems to be extremely limited in terms of the customizations I can make...
Any help would be appreciated?

The table view is view based but the header isn't and the header cells still are class NSTableHeaderCell. Use NSTableColumn's property headerCell. You can set the cell's properties like attributedStringValue and backgroundColor or replace the cells by instances of a subclass of NSTableHeaderCell and override one of the draw methods.

Play around with this to get inside the header.
Remember to except the answer if it works for you.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Color for the header.
let topColor = UIColor(red: (70/255.0), green: 000/255.0, blue: 000/255.0, alpha: 255)
//Location of label.
let locationOfLabel = self.view.frame.width
let headerView:UIView = UIView()
//Locating the text in the label
let title = UILabel(frame: CGRect(x: 0, y: 30, width: locationOfLabel, height: 21))
title.textAlignment = .center
//Changing the title in the label per the default.
let defaults:UserDefaults = UserDefaults.standard
defaults.synchronize()
let cardSelector = defaults.object(forKey: "selectorKeyID") as! Int
switch (cardSelector) {
case 0: title.text = "Personal"
break
case 1: title.text = "Saved"
break
case 2: title.text = "Favorite"
break
case 3: title.text = "Grouped"
break
default:
break
}
//Coloring the text in the label
//Add the label
title.textColor = UIColor.gray
headerView.addSubview(title)
//Adding a button to the header.
let closeBttn = UIButton(type: UIButtonType.system) as UIButton
closeBttn.frame = CGRect(x: 0, y: 30, width: 90, height: 27)
closeBttn.setTitle("Close", for: UIControlState())
closeBttn.setTitleColor(buttonColor, for: UIControlState())
closeBttn.titleLabel?.font = UIFont.systemFont(ofSize: 19, weight: UIFontWeightMedium)
closeBttn.addTarget(self, action: #selector(MainTableViewController.close), for: UIControlEvents.touchUpInside)
headerView.addSubview(closeBttn)
let menuButton = UIButton(type: UIButtonType.system) as UIButton
menuButton.frame = CGRect(x: locationOfLabel-53, y: 30, width: 27, height: 27)
menuButton.setBackgroundImage(UIImage(named: "VBC Menu4.png"), for: UIControlState())
menuButton.addTarget(self, action: #selector(MainTableViewController.menuButton), for: UIControlEvents.touchUpInside)
headerView.addSubview(menuButton)
//Coloring the header
headerView.backgroundColor = topColor
//Rounding the corners.
headerView.layer.cornerRadius = 10
headerView.clipsToBounds = true
return headerView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 70.0
}

Related

tableView Cell Labels Stack When I Scroll

I have a custom tableView, entirely created in code. IE, the cells need to be in code too.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
cell.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
cell.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
cell.addSubview(inventoryImage)
return cell
}
It works great, and you can see it loads perfectly. The top image is the load, the bottom image is once I scroll to the bottom. You can see the text labels seem to all stack on top of each other.
I would not recommend doing it this way.
It's better to move the code that creates namelabel, detailLabel and inventoryImage to an inventoryCell class derived from UITableViewCell.
But you can make it work.
Since table cells are reused and the reused cells already contain the created subviews, you need to either remove the subviews or treat cells with already created subviews differently.
And you should place your labels in the contentView of your UITableCellView.
To make your code work you can do this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "inventoryCell", for: indexPath)
// remove any subviews from contentView from recycled cells
cell.contentView.subviews.forEach{ $0.removeFromSuperview() }
let namelabel = UILabel()
namelabel.frame = CGRect(x: 145, y: 10 , width: 200, height: 30 )
namelabel.text = itemToShow.name
namelabel.font = UIFont.boldSystemFont(ofSize: 20)
namelabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(namelabel)
let detailLabel = UILabel()
detailLabel.frame = CGRect(x: 145, y: 50 , width: 200, height: 50 )
detailLabel.text = itemToShow.detail
detailLabel.backgroundColor = .clear
// add any label to contentView of the cell
cell.contentView.addSubview(detailLabel)
let inventoryImage = UIImageView()
inventoryImage.frame = CGRect(x: 10, y: 10, width: 130, height: 130)
inventoryImage.image = UIImage(named: "emptyInventorySlot")
// add any label to contentView of the cell
cell.contentView.addSubview(inventoryImage)
return cell
}
Better would be:
create a UITableViewCell subclass
place your label placement and configuration there
take care of cell reuses, override prepareForReuse() to support cell reuse
register this class as a table view cell for your tableView
cast your dequeued cell to your UITableViewCell subclass

Making UITableView drop down with stackview

I am trying to achieve a UITableView drop down when I click on a button. Initially the tableView should be hidden, and when user presses button, it should drop down. I have been trying to achieve this with a UIStackView but to no success. Maybe I am doing it wrong or maybe there is another approach do do this.
let stackView = UIStackView()
var btn: UIButton!
var myTableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
myTableView.delegate = self
myTableView.dataSource = self
myTableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 1))
myTableView.frame = CGRect(x: 0, y: 0, width: 300, height: 200)
myTableView.center = CGPoint(x: self.view.frame.width/2, y: self.view.frame.height/2)
myTableView.showsVerticalScrollIndicator = false
btn = UIButton(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 50))
btn.backgroundColor = UIColor.blue.withAlphaComponent(0.3)
btn.setTitle("DropDownMenu", for: UIControlState.normal)
btn.titleLabel?.textColor = .white
btn.titleLabel?.textAlignment = .center
btn.center = CGPoint(x: self.view.frame.width/2, y: myTableView.center.y - myTableView.frame.height/2 - btn.frame.height/2)
btn.addTarget(self, action: #selector(btnPressed), for: UIControlEvents.touchUpInside)
stackView.axis = UILayoutConstraintAxis.vertical
stackView.distribution = UIStackViewDistribution.equalSpacing
stackView.alignment = UIStackViewAlignment.center
stackView.spacing = 16.0
stackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(btn)
stackView.addSubview(myTableView)
self.view.addSubview(stackView)
}
#objc func btnPressed() {
UIView.animate(withDuration: 1) {
self.myTableView.isHidden = !self.myTableView.isHidden
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "This is cell " + indexPath.row.description
cell.textLabel?.textColor = .black
return cell
}
I can get the tableView to disappear but with no animations. Any thoughts?
The approach I ended up taking was not going via a UIStackView but insead simply having a button that animates the tableView's frame. The frame is initially set to the width of the screen and a height of 0. When user presses button, I set the height.
UIView.animate(withDuration: 0.25, animations: {
self.menuTable.frame = CGRect(x: 0, y: (sender.center.y + sender.frame.height/2), width: self.view.frame.width, height: yourHeight)
})

Varying separator colours in UITableView

I have an account page in my app where I would like to have account information displayed above the first section in a tableview, similar to the Apple ID page in the settings.
My question is based around how to structure the view such that there is no separator shown above the profile picture. Or to make it that the information appears to be within the tableview but not within a section.
From reading around it seems either not possible or very difficult to have some separators showing and others not.
In the case of the apple ID page, is that picture, name and email in its own section or a cell that is part of the section beneath it?
The following picture is the closest I have got although that top separator proves difficult to hide.
Apologies for the broad nature of the question, however i think the aim is clear and once a solution is reached I will make the question more specific for future viewers.
Thanks.
Try this
//This will hide seperator for all rows
tableView.separatorStyle = UITableViewCellStyle.None
//For custom seperator
if indexPath.row != 0{
var aView: UIView = UIView()
aView.frame = CGRect(x:0,y:44,width:300,height:1)
aView.backgroundColor = UIColor.redColor()
cell .addSubview(aView)
}
You need to use custom view in your UITableViewCell for that.
1. Set UITableView Separator to none in storyboard.
2. Create a custom UITableViewCell with a UIView in it.
class TableCell: UITableViewCell
{
#IBOutlet weak var separatorView: UIView!
}
3. Set the backgroundColor of separatorView in cellForRowAt according to your requirement.
Example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableCell
switch indexPath.row
{
case 0:
cell.backgroundColor = .red
case 1:
cell.backgroundColor = .green
case 2:
cell.backgroundColor = .blue
case 3:
cell.backgroundColor = .yellow
default:
cell.backgroundColor = .orange
}
return cell
}
Screenshot:
Thanks Dev and PGDev, I was able to come up with a nice solution that worked well for static cells. I made an extension for UITableViewCell that looks like this.
let separatorColour = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 0.5)
extension UITableViewCell {
func separator(topInset: CGFloat, btmInset: CGFloat, showTop: Bool, showBtm: Bool) {
if showTop {
let topSeparator: UIView = UIView()
topSeparator.frame = CGRect(x: topInset, y: 0, width: frame.width, height: 0.5)
topSeparator.backgroundColor = separatorColour
addSubview(topSeparator)
}
if showBtm {
let btmSeparator: UIView = UIView()
btmSeparator.frame = CGRect(x: btmInset, y: frame.height - 0.5, width: frame.width, height: 0.5)
btmSeparator.backgroundColor = separatorColour
addSubview(btmSeparator)
}
}
func btnSeparator() {
let topSeparator: UIView = UIView()
topSeparator.frame = CGRect(x: 0, y: 0, width: frame.width, height: 0.5)
topSeparator.backgroundColor = separatorColour
addSubview(topSeparator)
let btmSeparator: UIView = UIView()
btmSeparator.frame = CGRect(x: 16, y: frame.height - 0.5, width: frame.width - 32, height: 0.5)
btmSeparator.backgroundColor = separatorColour
addSubview(btmSeparator)
}
}
In the first function separator() you can simply choose the inset for top and bottom separator and whether they are visible or not.
TopCell
So in the case of the cell with the name label shown in the picture above, both top and bottom separators are visible. However because it is supposed to appear as the first cell in the section, it has no top inset and an appropriate bottom inset.
MiddleCell
In the case of the cell with the email label it only has the lower separator (with inset) visible as the cell above has a bottom separator.
BottomCell
For the cell with the password label, it only has the lower separator (without offset) visible as the cell above has a bottom separator.
SpecialCase
The btnSeparator() function was just for the case of the help and support button, where I wanted inset on both sides.
Now to implement this extension, I used a switch statement inside the cellForRowAt function to give each cell the appropriate extension and this is what that looks like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
let inset: CGFloat = 16
switch indexPath {
// First Section
case [0,1]:
cell.separator(topInset: 0, btmInset: inset, showTop: true, showBtm: true)
case [0,2]:
cell.separator(topInset: inset, btmInset: inset, showTop: false, showBtm: true)
case [0,3]:
cell.separator(topInset: inset, btmInset: 0, showTop: false, showBtm: true)
// Second Section
case [1,0]:
cell.separator(topInset: 0, btmInset: inset, showTop: true, showBtm: true)
case [1,1]:
cell.separator(topInset: inset, btmInset: inset, showTop: false, showBtm: true)
case [1,2]:
cell.separator(topInset: inset, btmInset: 0, showTop: false, showBtm: true)
// Third Section
case [2,0]:
cell.btnSeparator()
case [2,1]:
cell.separator(topInset: inset, btmInset: 0, showTop: false, showBtm: true)
case [2,3]:
cell.separator(topInset: 0, btmInset: 0, showTop: true, showBtm: true)
default:
print("default")
}
return cell
}
Be sure to set separator to none in the Storyboard or use:
tableView.separatorStyle = UITableViewCellStyle.None

How to add the vertical alignment between Button and label programmatically

Rather creating a Xib file and loading it into tableview. I am creating a label and button in Header view.
var btnTimeZone = UIButton(type: .Custom)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.btnTimeZone.titleLabel?.font = UIFont.boldSystemFontOfSize(14.0)
self.btnTimeZone.backgroundColor = UIColor.clearColor()
self.btnTimeZone.setTitleColor(UIColor.blackColor(), forState: .Normal)
self.btnTimeZone.addTarget(self, action: #selector(self.selectClicked), forControlEvents: UIControlEvents.TouchUpInside)
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 150))
headerView.layer.borderWidth = 2
headerView.layer.borderColor = UIColor.whiteColor().CGColor
headerView.backgroundColor = ClientConfiguration.primaryUIColor()
let myLabel = UILabel()
myLabel.frame = CGRectMake(0, 0, tableView.frame.width - 70, 30)
myLabel.font = UIFont.boldSystemFontOfSize(10)
myLabel.backgroundColor = ClientConfiguration.primaryUIColor()
myLabel.textColor = UIColor.whiteColor()
myLabel.textAlignment = .Left
myLabel.text = "please Select your Time Zone"
let frame = CGRectMake(0, 0,headerView.frame.width , 50)
self.btnTimeZone.frame = frame
headerView.addSubview(myLabel)
headerView.addSubview(self.btnTimeZone)
return headerView
}
I want label above button in header view but I am not able to this..??
how can I do this..??
You can use something like the following to add a layout constraint via code:
let top = NSLayoutConstraint(item:myLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:headerView, attribute: NSLayoutAttribute.top, multiplier:1.0, constant:0)
headerView.addConstraint(top)
Of course the issue would be that one constraint alone might not be enough and you'd need to add others to control the spacing between the label and the button and to also control the horizontal spacing for the elements.

Update footer in UITableView

Have custom footer view in my UITableView:
public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { // custom view for footer. will be adjusted to default or specified footer height
return footer()
}
func footer() -> UILabel {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: (navigationController?.navigationBar.bounds.size.height)!))
label.backgroundColor = AppColors.Bordo.color
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = .white
label.text = "Selected \(self.selectedGenres.count) of \(self.genres.count)"
label.textAlignment = .center
return label
}
When user select/deselect row in table view I want refresh my footer with info of selected rows. How I can do it without reloading whole tableview?
And what method footerView(forSection: indexPath.section) of UITableView does?
create a global object of label... and init it only when the label is nil... and you can access this label anywhere in code.
let globalLabel : UILabel ?
public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { // custom view for footer. will be adjusted to default or specified footer height
return footer()
}
func footer() -> UILabel {
if (globalLabel == nil) {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: (navigationController?.navigationBar.bounds.size.height)!))
label.backgroundColor = AppColors.Bordo.color
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = .white
label.textAlignment = .center
globalLabel = label
}
globalLabel.text = "Selected \(self.selectedGenres.count) of \(self.genres.count)"
return globalLabel
}
Done it using Rajesh Choudhary proposal and computing property:
var selectedGenres: [Genre] = [] {
didSet {
self.footerForTableView.text = titleForFooter
}
}
var titleForFooter: String {
return "Selected \(self.selectedGenres.count) of \(self.genres.count)"
}
lazy var footerForTableView: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: (self.navigationController?.navigationBar.bounds.size.height)!))
label.backgroundColor = AppColors.Bordo.color
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = .white
label.text = self.titleForFooter
label.textAlignment = .center
return label
}()