How to change font size of NSTableHeaderCell - swift

I am trying to change the font size of my NSTableView within my code to allow the user to change it to their liking.
I was successful by changing the font size of each NSTableCellView but failed to do so by the header cells.
I was trying to do it like this
let headerCell = NSTableHeaderCell()
let font = NSFont(name: "Arial", size: 22.0)
headerCell.stringValue = "firstname"
headerCell.font = font
customerTable.tableColumns[0].headerCell = headerCell
The stringValue of the header cell will be set accordingly but the size does not change. How can I change the font size of my headers?
Thanks
Oliver

So, finally I was only able to solve this with subclassing NSTableHeaderCell. It was somehow strange as Swift and Cocoa always tend to favor composition over inheritance but anyway.
Swift 3.1
final class CustomTableHeaderCell : NSTableHeaderCell {
override init(textCell: String) {
super.init(textCell: textCell)
self.font = NSFont.boldSystemFont(ofSize: 18) // Or set NSFont to your choice
self.backgroundColor = NSColor.white
}
required init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
// skip super.drawWithFrame(), since that is what draws borders
self.drawInterior(withFrame: cellFrame, in: controlView)
}
override func drawInterior(withFrame cellFrame: NSRect, in controlView: NSView) {
let titleRect = self.titleRect(forBounds: cellFrame)
self.attributedStringValue.draw(in: titleRect)
}
}

Changing attributedStringValue will do the trick
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableColumns.forEach { (column) in
column.headerCell.attributedStringValue = NSAttributedString(string: column.title, attributes: [NSFontAttributeName: NSFont.boldSystemFont(ofSize: 18)])
// Optional: you can change title color also jsut by adding NSForegroundColorAttributeName
}
}
https://i.stack.imgur.com/IXJyu.png

You can create a NSTableHeaderCell subclass and implement the property you want to change.
In Objective-C (I'm not good at Swift):
#implementation CustomTableHeaderCell
-(NSFont *)font {
return [NSFont fontWithName:#"Arial" size:22];
}
// you can alse custom textColor
-(NSColor *)textColor {
return [NSColor redColor];
}
#end
Assign CustomTableHeaderCell:
CustomTableHeaderCell *headerCell = [[CustomTableHeaderCell alloc] init];
headerCell.stringValue = #"Header title";
self.tableView.tableColumns[0].headerCell = headerCell;
In Cocoa, there are many things you can't change its style by cell.font = ..., you need to create a subcalss.

Related

get rid of the space under the text of UITextView

I'm currently working on making a custom UITextView class.
As you can see in the image, there is bigger space between the text in line 1 and line 2.
The blue cursor represents the height of the text, however, like line 2's text, the gray background has a height around 2 times bigger than the text.
After some research, I thought change the NSMutableParagraphStyle's lineSpacing will fix the problem I have (but it didn't). Is changing the lineSpacing or MutableParagraphStyle won't change the line-height?
import UIKit
class CustomTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init() {
super.init(frame: .zero, textContainer: nil)
configure()
}
private func configure() {
translatesAutoresizingMaskIntoConstraints = false
font = UIFont.myCustomFont()
let textStyle = NSMutableParagraphStyle()
textStyle.lineSpacing = 1.0
textStyle.paragraphSpacing = 1.0
let attributes: Dictionary = [NSAttributedString.Key.paragraphStyle: textStyle]
attributedText = NSAttributedString(string: self.text, attributes: attributes)
}
override func caretRect(for position: UITextPosition) -> CGRect {
var superRect = super.caretRect(for: position)
guard let font = self.font else { return superRect }
superRect.size.height = font.pointSize
return superRect
}
}
Added this part later...
So, when I delete the line for setting the font, I was able to get rid of the bigger space (but lost the custom font).
So I think the custom font has something like a bottom padding? I guess. I created a font like this, but do you see any problem or any clue why I get the bottom space, like the gray area in the image?
extension UIFont {
static func myCustomFont() -> UIFont {
return UIFont(name: "Custom Font", size: 18)!
}
}

Label Colours on an #IBDesignable View appearing different of different devices

I have an #IBDesignable custom view. It contain an UIView with two subclassed UILabels. The custom subclass of UILabel is setting it's font.
What I'm trying to achieve is by changing by making the background colour of the view an inspectable property that the text colour change appropriately to be legible.
My code is below. Custom.Colour.<name> is just an enum of defined colours.
#IBDesignable
class CustomMiniView: UIView, NibLoadable {
public var view:UIView!
#IBOutlet weak var colourView: UIView!
#IBOutlet weak var headingLabel: CustomUILabel!
#IBOutlet weak var amountLabel: CustomUILabel!
#IBInspectable var blockColor:UIColor! {
didSet {
self.colourView.backgroundColor = blockColor
switch blockColor {
case Custom.Colour.darkBlue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.blue:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightBlue:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.green:
headingLabel.textColor = .white
amountLabel.textColor = .white
case Custom.Colour.lightGreen:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.blue
case Custom.Colour.yellow:
headingLabel.textColor = Custom.Colour.grey
amountLabel.textColor = Custom.Colour.grey
default : printError("We have not handled text colours for a background of this colour. = \(blockColor.hexString)")
}
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
self.setupFromNib()
self.commonInit()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setupFromNib()
self.commonInit()
}
func commonInit() {
self.backgroundColor = .clear
}
}
This was working for me fine, however I was then getting report that the text was showing up as white everywhere and got sent a screen shot which confused me. When I ran this in a simulator and a different device I was able to see this not working. Here are two screen shots of what is happening on my iPad and what I expect to happen and a screen shot of what is happening on some other devices and the simulator.
This is what is happening on my device and the expected result.
This is whats happening on other devices and the incorrect result.
Is there a reason this would appeared different of different devices? I'm at a loss to the cause or how to fix this.
Thanks to comments above I've found a fix.
Rather than doing a switch on the colour I've done a switch on the .hexString of the colour which has worked.
switch blockColor.hexString {
case Custom.Colour.darkBlue.hexString :
for the record hexString is an extension to UIColor
var hexString: String {
let colorRef = cgColor.components
let r = colorRef?[0] ?? 0
let g = colorRef?[1] ?? 0
let b = ((colorRef?.count ?? 0) > 2 ? colorRef?[2] : g) ?? 0
let a = cgColor.alpha
var color = String(
format: "#%02lX%02lX%02lX",
lroundf(Float(r * 255)),
lroundf(Float(g * 255)),
lroundf(Float(b * 255))
)
if a < 1 {
color += String(format: "%02lX", lroundf(Float(a)))
}
return color
}
I'm still not sure why switching on hexString worked and the UIColor in the enum did not work consistently but thanks to comments about that helped me find a solution to fix it myself. Question for another time is why switching on UIColor would be unreliable but now back to work.

Using a CALayer in an NSStatusBarButton

I'm interested in drawing custom content in a NSStatusBarButton similar to the built-in battery menu bar app:
I'd like to use a CALayer due to the flexibility of drawing custom content. I've managed to add a layer-backed NSView to the button using this code:
// StatusView
class StatusView: NSView {
required init?(coder: NSCoder) {
super.init(coder: coder)
self.wantsLayer = true
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
if let layer = self.layer {
layer.backgroundColor = NSColor.clear.cgColor
// Code adding text layer...
}
}
}
// MenuController
class StatusMenuController: NSObject {
override func awakeFromNib() {
if let button = statusItem.button {
layerView.frame = NSRect(x: 0.0, y: 0.0, width: len / 2, height: button.bounds.height)
button.addSubview(layerView)
}
}
}
Basically I'm adding a CALayer to the button of the NSStatusBarButton, which looks fine normally, but seems to have an opaque background when you click the menu item:
I've set the background color of the CALayer to transparent and the isOpaque property of the layer's view is false. Is there a way to get the view to actually blend with the background, similar to the battery menu bar app?
Thanks!

Swift: Programmatically make UILabel bold without changing its size?

I have a UILabel created programmatically. I would like to make the text of the label bold without specifying font size. So far I have only found:
UIFont.boldSystemFont(ofSize: CGFloat)
This is what I have exactly:
let titleLabel = UILabel()
let fontSize: CGFloat = 26
titleLabel.font = UIFont.boldSystemFont(ofSize: titleLabelFontSize)
But this way I am also setting the size. I would like to avoid that. Is there a way?
If there is no way, what would be a good workaround in Swift?
Thank you!
Why not just:
titleLabel.font = UIFont.boldSystemFont(ofSize: titleLabel.font.pointSize)
To just make the Font bold without altering the font size you could create an extension like this (which is based off the answer here:
extension UIFont {
func withTraits(traits:UIFontDescriptorSymbolicTraits...) -> UIFont {
let descriptor = self.fontDescriptor()
.fontDescriptorWithSymbolicTraits(UIFontDescriptorSymbolicTraits(traits))
return UIFont(descriptor: descriptor, size: 0)
}
func bold() -> UIFont {
return withTraits(.TraitBold)
}
}
So that way you could use it like this:
let titleLabel = UILabel()
titleLabel.font = titleLabel.font.bold() //no need to include size!
Update for Swift 4 syntax:
extension UIFont {
func withTraits(traits:UIFontDescriptorSymbolicTraits...) -> UIFont {
let descriptor = self.fontDescriptor
.withSymbolicTraits(UIFontDescriptorSymbolicTraits(traits))
return UIFont(descriptor: descriptor!, size: 0)
}
func bold() -> UIFont {
return withTraits(traits: .traitBold)
}
}

Setting backgroundColor of custom NSView

What is the process of drawing to NSView using storyboards for osx? I have added a NSView to the NSViewController. Then, I added a few constraints and an outlet.
Next, I added some code to change the color:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blueColor().CGColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}
I would like to do custom drawing and changing colors of the NSView. I have
performed sophisticated drawing on iOS in the past, but am totally stuck here.
Does anyone see what I'm doing wrong?
The correct way is
class ViewController: NSViewController {
#IBOutlet var box: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
}
override func viewWillAppear() {
box.layer?.backgroundColor = NSColor.blue.cgColor
//box.layer?.setNeedsDisplay()
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}}
Swift via property
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(CGColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.CGColor
}
}
}
Usage:
yourView.backgroundColor = NSColor.greenColor()
Where yourView is NSView or any of its subclasses
Updated for Swift 3
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
edit/update:
Another option is to design your own colored view:
import Cocoa
#IBDesignable class ColoredView: NSView {
#IBInspectable var backgroundColor: NSColor = .clear
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
backgroundColor.set()
dirtyRect.fill()
}
}
Then you just need to add a Custom View NSView and set the custom class in the inspector:
Original Answer
Swift 3.0 or later
extension NSView {
var backgroundColor: NSColor? {
get {
guard let color = layer?.backgroundColor else { return nil }
return NSColor(cgColor: color)
}
set {
wantsLayer = true
layer?.backgroundColor = newValue?.cgColor
}
}
}
let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
myView.backgroundColor = .red
This works a lot better:
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
NSColor.blueColor().setFill()
NSRectFill(dirtyRect);
}
Best way to set a NSView background colour in MacOS 10.14 with dark mode support :
1/ Create your colour in Assets.xcassets
2/ Subclass your NSView and add this :
class myView: NSView {
override func updateLayer() {
self.layer?.backgroundColor = NSColor(named: "customControlColor")?.cgColor
}
}
Very simple and dark mode supported with the colour of your choice !
Full guide : https://developer.apple.com/documentation/appkit/supporting_dark_mode_in_your_interface
Just one line of code is enough to change the background of any NSView object:
myView.setValue(NSColor.blue, forKey: "backgroundColor")
Instead of this, you can also add an user defined attribute in the interface designer of type Color with keyPath backgroundColor.
Update to Swift 3 solution by #CryingHippo (It showed colors not on every run in my case). I've added DispatchQueue.main.async and now it shows colors on every run of the app.
extension NSView {
var backgroundColor: NSColor? {
get {
if let colorRef = self.layer?.backgroundColor {
return NSColor(cgColor: colorRef)
} else {
return nil
}
}
set {
DispatchQueue.main.async {
self.wantsLayer = true
self.layer?.backgroundColor = newValue?.cgColor
}
}
}
}
None of the solutions is using pure power of Cocoa framework.
The correct solution is to use NSBox instead of NSView. It has always supported fillColor, borderColor etc.
Set titlePosition to None
Set boxType to Custom
Set borderType to None
Set your desired fillColor from asset catalogue (IMPORTANT for dark mode)
Set borderWidth and borderRadius to 0
Bonus:
it dynamically reacts to sudden change of appearance (light to dark)
it supports animations ( no need for dynamic + no need to override animation(forKey key:NSAnimatablePropertyKey) ->)
future macOS support automatically
WARNING:
Using NSBox + system colors in dark mode will apply tinting corectly. If you do not want tinting use catalogue color.
Alternative is to provide subclass of NSView and do the drawing updates in updateLayer
import Cocoa
#IBDesignable
class CustomView: NSView {
#IBInspectable var backgroundColor : NSColor? {
didSet { needsDisplay = true }
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
guard let layer = layer else { return }
layer.backgroundColor = backgroundColor?.cgColor
}
}
Tinting:
Since macOS 10.13 (High Sierra) NSView responds to selector backgroundColor although it is not documented!