NSTextField font styling resets when selected - macos

Context:
In Interface Builder I have a non-editable label (NSTextField). The contents of the label is created using Cocoa Bindings. The value of the binding is an NSAttributedString (created using a talue transformer). See image:
The value transformer essentially specifies the font for specific characters, as per Markdown formatting (i.e. Italic and Bold). Such that String --> NSAttributedString. The label's attributedStringValue is changed appropriately
Issue:
When selecting the label in the UI. The font resets to what is specified in IB, and not what was set as the NSAttributedString. If you don't select the text then everything looks good.
Before clicking on the label:
After clicking/selected the label:
Attempted Solutions:
I've tried subclassing NSTextField but there's nothing really to override that enables me to disable any font changes when the text is selected.
I've tried disabling rich text. This actually helps a lot by not changing the normal text, but it still strips the formatting from the bold and italic text
Most similar issues out there are with NSTextViews not NSTextFields

You need to set allowsEditingTextAttributes = true
For example:
class ViewController: NSViewController {
dynamic var markdownText : String?
#IBOutlet weak var label: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
label.allowsEditingTextAttributes = true
}
}
Then you can select text as in my example:
Here is the code:https://github.com/emankovski/BindingFormattedText

I believe I found the solution to this issue, which seems unsolved based on the original poster's comments above.
I had an NSTextField that was selectable but not editable. The text was set by creating NSAttributedString with font and text color attributes, and passing this to the NSTextField's setAttributedStringValue method.
The problem as that the line spacing of the text was wrong until I clicked a text field and then clicked a different text field, as shown in the GIF below. Once the clicking was done, the text displayed properly.
Upon examining the text attributes of the NSTextField before and after editing, I noticed that NSOriginalFont was set to .AppleSystemUIFont 12pt instead of my font, which was Helvetica Neue 11pt.
BEFORE
NSFont = "\"HelveticaNeue 11.00 pt. P [] (0x600000e46400) fobj=0x1040163d0, spc=3.06\"";
NSOriginalFont = "\".AppleSystemUIFont 12.00 pt. P [] (0x600000d8f0f0) fobj=0x10361b1a0, spc=3.39\"";
AFTER:
NSFont = "\"HelveticaNeue 11.00 pt. P [] (0x600000e46400) fobj=0x1040163d0, spc=3.06\"";
NSOriginalFont = "\"HelveticaNeue 11.00 pt. P [] (0x600000e46400) fobj=0x1040163d0, spc=3.06\"";
I solved the problem by setting the NSAttributedString's font attribute not only NSFontAttributeName (i.e., #"NSFont") but also #"NSOriginalFont".
[controlText removeAttribute:NSFontAttributeName range:(NSMakeRange(0, len))];
[controlText addAttribute:NSFontAttributeName value:font range:(NSMakeRange(0, len))];
[controlText removeAttribute:#"NSOriginalFont" range:(NSMakeRange(0, len))];
[controlText addAttribute:#"NSOriginalFont" value:font range:(NSMakeRange(0, len))];

Swift 5 solution
First you need to set this property for your NSTextField
cell.textField?.allowsEditingTextAttributes = true
Then, where you are creating the NSAttributedString, do this first
// Default font and color, to be used where no attritubes are set
let default_font = NSFont(name: "Courier New", size: 14)
let default_color = NSColor.white
let entire_str_range = NSMakeRange(0, text.string.count)
mutable_attr_str.removeAttribute(NSAttributedString.Key.font, range: entire_str_range)
mutable_attr_str.addAttribute(NSAttributedString.Key.font, value: default_font!, range: entire_str_range)
mutable_attr_str.removeAttribute(NSAttributedString.Key.foregroundColor, range: entire_str_range)
mutable_attr_str.addAttribute(NSAttributedString.Key.foregroundColor, value: default_color, range: entire_str_range)
mutable_attr_str.removeAttribute(NSAttributedString.Key(rawValue: "NSOriginalFont"), range: entire_str_range)
mutable_attr_str.addAttribute(NSAttributedString.Key(rawValue: "NSOriginalFont"), value: default_font!, range: entire_str_range)
Then set up your NSAttributedString however you like. When clicked, it should keep your additional attributes, and the parts of the string with no attributes will default to the above font and color.

Related

NSAppearance is not updating when toggling dark mode

I have a macOS app that runs only in the macOS status bar. I changed the "Application is agent (UIElement)" property in the Info.plist to "YES":
<key>LSUIElement</key>
<true/>
I have a timer that prints out the appearance's name every 5 seconds like this:
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
let appearance = NSAppearance.currentDrawing()
print(appearance.name)
}
Problem
The name doesn't actually change when I toggle dark/light mode in system settings. It always prints the name of the appearance that was set when the application launched.
Is there a way to listen to system appearance changes?
Goal
My end goal is actually to draw an NSAttributedString to an NSImage, and use that NSImage as the NSStatusItem button's image.
let image: NSImage = // generate image
statusItem.button?.image = image
For the text in the attributed string I use UIColor.labelColor that is supposed to be based on the system appearance. However it seems to not respect the system appearance change.
When I start the application in Dark Mode and then switch to Light Mode:
When I start the application in Light Mode and then switch to Dark Mode:
Side note
The reason why I turn the NSAttributedString into an NSImage and don't use the NSAttributedString directly on the NSStatusItem button's attributedTitle is because it doesn't position correctly in the status bar.
The problem with drawing a NSAttributedString is, that NSAttributedString doesn't know how to render dynamic colors such as NSColor.labelColor. Thus, it doesn't react on appearance changes. You have to use a UI element.
Solution
I solved this problem by passing the NSAttributedString to a NSTextField and draw that into an NSImage. Works perfectly fine.
func updateStatusItemImage() {
// Use UI element: `NSTextField`
let attributedString: NSAttributedString = ...
let textField = NSTextField(labelWithAttributedString: attributedString)
textField.sizeToFit()
// Draw the `NSTextField` into an `NSImage`
let size = textField.frame.size
let image = NSImage(size: size)
image.lockFocus()
textField.draw(textField.bounds)
image.unlockFocus()
// Assign the drawn image to the button of the `NSStatusItem`
statusItem.button?.image = image
}
React on NSAppearance changes
In addition, since NSImage doesn't know about NSAppearance either I need to trigger a redraw on appearance changes by observing the effectiveAppearance property of the button of the NSStatusItem:
observation = statusItem.observe(\.button?.effectiveAppearance, options: []) { [weak self] _, _ in
// Redraw
self?.updateStatusItemImage()
}

How can I set NSTableView column to use monospaced numbers?

El Capitan introduced San Francisco system font, which has proportional digits by default.
This makes numbers in table columns look jagged and hard to compare:
I'd like to enable fixed-width numbers option for the font, but keep using the default system font and keep backwards compatibility with earlier versions of OS X.
In Interface Builder selecting font > Font Panel > Typography > Monospaced Numbers does not affect the font (XIB file remains unchanged).
What's the right way to set monospaced numbers in OS X table view columns? (I suspect IB is unusable for this, so a programmatic solution is OK too).
Just use +[NSFont monospacedDigitSystemFontOfSize:weight:] when it's available. It's new in 10.11, but still not in the NSFont docs. It's in the headers and was discussed in the WWDC 2015 videos. So, something like:
if ([NSFont respondsToSelector:#selector(monospacedDigitSystemFontOfSize:weight:)])
textField.font = [NSFont monospacedDigitSystemFontOfSize:textField.font.pointSize weight:NSFontWeightRegular];
Here's a Swift extension that gives you a monospaced digits font with high legibility.
extension NSFont {
var legibleNumbersVariant: NSFont {
let features = [
[NSFontFeatureTypeIdentifierKey: kNumberSpacingType,
NSFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector],
[NSFontFeatureTypeIdentifierKey: kStylisticAlternativesType,
NSFontFeatureSelectorIdentifierKey: kStylisticAltSixOnSelector]
]
let descriptor = fontDescriptor.addingAttributes([NSFontFeatureSettingsAttribute: features])
return NSFont(descriptor: descriptor, size: pointSize) ?? self
}
}
Treat the following as pseudo-code, quickly done, not throughly tested, etc.
Given an NSFont which represents a font which has monospaced numbers as a feature the following method will produce another NSFont with that feature selected:
- (NSFont *) newMonospaceNumbersFont:(NSFont *)font
{
CTFontDescriptorRef origDesc = CTFontCopyFontDescriptor((__bridge CTFontRef)font);
CTFontDescriptorRef monoDesc = CTFontDescriptorCreateCopyWithFeature(origDesc, (__bridge CFNumberRef)#(kNumberSpacingType), (__bridge CFNumberRef)#(kMonospacedNumbersSelector));
CFRelease(origDesc);
CTFontRef monoFont = CTFontCreateWithFontDescriptor(monoDesc, font.pointSize, NULL);
CFRelease(monoDesc);
return (__bridge_transfer NSFont *)monoFont;
}
You can use this, say, to take the current font of a UI element and convert it to one with monospace numbers.
HTH
Variant for Swift
Assuming res is the NSTextField with the number to display:
let origDesc = CTFontCopyFontDescriptor(res.font!)
let monoDesc = CTFontDescriptorCreateCopyWithFeature(origDesc, kNumberSpacingType, kMonospacedNumbersSelector)
let monoFont = CTFontCreateWithFontDescriptor(monoDesc, res.font!.pointSize, nil)
res.font = monoFont
In my experience, the "font panel" functionality isn't well defined and I usually just ignore it whenever I'm messing with a XIB or Storyboard.
What should work is to go back to that "Font" attribute in the Text Field Cell attributes inspector and then select "User Fixed Pitch" from the Font drop down menu (the choice should automatically default to size 11).
If you bump the font size up a point, it'll magically switch to Monaco (the default fixed width font).

How do I limit NSTextView to 2 lines?

I'm trying to specify the number of lines for NSTextView. My designer is requesting 2 lines of text max. I've tried NSMutableParagraph style to add the ellipses truncation that I want, but with NSMutableParagraph I can only get NSTextView with 1 line and without NSMutableParagraph, I get a scrolling text with as many lines as needed to complete text.
var attributedString = NSMutableAttributedString(string: "This is my text, I can keep going for many characters")
var para = NSMutableParagraphStyle()
para.lineBreakMode = NSLineBreakMode.ByTruncatingTail
let globalAttributes = [
NSParagraphStyleAttributeName: para
]
let range = NSRange(location:0, length: attributedString.length)
attributedString.addAttributes(globalAttributes, range: range)
cellView.myTextView!.textStorage?.setAttributedString(attributedString)
I've tried height constraint on NSTextView. I've tried:
cellView.myTextView!.textContainer?.containerSize = NSMakeSize(300, 32)
I've tried creating IBOutlet for NSScrollView that NSTextView in within and adjusting its height. No luck with getting both 2 lines and truncation. Any help is greatly appreciated. I feel like I'm just missing a method or setup. Thanks!
From 10.11 you can use this
yourTextViewObj.textContainer.maximumNumberOfLines = 2;
You can use an NSTextField configured as a multi-line label. That means setting its cell's wraps property to true and, if desired, its truncatesLastVisibleLine to true.
For NSTextField (aka label) You can just do self.textField.maximumNumberOfLines = 2;
That's it.
Max number of lines is now a property of NSTextField
label.maximumNumberOfLines = 1;

UITextField not scrolling horizontally

I am trying to make a small calculator app.
When a UIButton is pressed, the Button title is added to a UITextField.
kind of:
myuitextfield.text = [myuitextfield.text stringByAppendingString:[button currentTitle];
When I reach the end of my textfield, the text gets truncated. How can I disable this, so the textfield starts scrolling automatically and allows adding more characters?
I tried every possible option in Interface Builder, without any luck.
Isn't the UITextField supposed to scroll automatically? I can see this behavior when a native keyboard is used and text is entered.
I have chosen UITextField, as I need only 1 Line.
To illustrate the Problem:
When I enter text using my custom UIButtons text gets truncated
When I tap the UITextField and enter text using the keyboard I can enter unlimited text and the text is not truncated.
If you are facing this issue on iOS7, I've managed to fix it after been inspired by this post. In my case I had a field for entering an email address and after reaching the edge, the user could carry on typing but the text would be invisible (off-field).
First, add a callback to your UITextField so that you can track a text change to the field:
[self.field addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
Then evaluate the size in pixels of the entered string as it is typed and change the text alignment from left to right when reaching the edge of the field area:
- (void)textFieldDidChange:(NSNotification *)aNotif{
float maxNumPixelsOnScreen = 235; // Change this value to fit your case
CGSize maximumSize = CGSizeMake(maxNumPixelsOnScreen + 10, 1);
NSString *aString = self.field.text;
CGSize stringSize = [aString sizeWithFont:fieldFont
constrainedToSize:maximumSize
lineBreakMode:NSLineBreakByWordWrapping];
self.field.textAlignment = NSTextAlignmentLeft;
if (stringSize.width >= maxNumPixelsOnScreen)
self.field.textAlignment = NSTextAlignmentRight;
}
Note:
self.field is the offending UITextField
maximumSize: I'm adding 10 the the width to be slightly over the limit defined
fieldFont is the UIFont used to render the text field
Hope it helps!
you have to add UITextview and limit the number of lines to 2.Textfield doesnt work with two lines.Textview is same as textfields except the delegates and some properties differ.

Xcode: Font in storyboard looks different than by code

I have a storyboard with labels. The label's font is set to System 25.
I wanted to make the font size dynamic, and I set it by code now.
I set breakpoints, so I know that "25" is actually chosen in my code, but the font size still is smaller than when I set it in the storyboard designer.
Does anybody perhaps spot where I might have gone wrong or any caveats that I might have missed?
//set label font size
CGFloat nFontSize;
if (bIsIPad)
{
nFontSize=25.0;
}
else if (bIsIPhone_3GS_4_4s_Or_iPodTouch_3_4)
{
nFontSize=12.0;
}
else if (bIsIphone_5_Or_IPodTouch_5)
{
nFontSize=25.0;
}
UIFont *nFont = [UIFont fontWithName:#"System" size:nFontSize];
captionLabel0.font = nFont;
captionLabel1.font = nFont;
It seems that "fontWithName:#"System"" is not the same as "[UIFont systemFontOfSize:nFontSize];"
The storyboard property page seems to reflect "systemFontOfSize" when it displays the font "System".
When I chose "systemFontOfSize", the results were the same as in the storyboard.
You need to connect your label to outlet (storyboard).
And after that, you set the font size for this label.

Resources