I'm using an NSAttributedString's draw(at:) function inside of an NSView to render some text in a custom font inside my window.
However, the font looks weirdly blurry and "too heavy" when run on a non-retina MacBook (both on the internal display & on an external LCD).
Since I'm able to perfectly reproduce the desired outcome in Sketch on the same machine, I'm assuming this to be an issue with my code.
Here's my code so far:
import Cocoa
class StepNameLabel: NSView {
// Im aware that this is not the intended way of loading Apple's system fonts
// However, this was the best way I could find to make sure that both Sketch
// and the app were using the exact same fonts.
var font = NSFont(name: "SF Pro Text Semibold", size: 22)
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let text = "Choose your Images"
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: NSColor.white
]
print(font?.fontName)
let drawableString = NSAttributedString(string: text, attributes: attributes)
frame.size = drawableString.size()
drawableString.draw(at: NSPoint(x: 0, y: 0))
}
}
And here's a screenshot showing the difference between the Sketch-File & the app running on the same display (left: Graphic in Sketch app, right: The output of above code):
The app's code & the Sketch graphic both use Apple's "SF Pro Text" font with a font-weight of "Semibold" at a size of 22 units.
Any help in finding out what's going wrong here would be greatly appreciated.
This might be the infamous 'half pixel' problem. Try:
drawableString.draw(at: NSPoint(x: 0.5, y: 0.5))
There is some information about this here (search the page for 'Points and Pixels').
Related
I am usinig CIAttributedTextImageGenerator to generate text into CIImage in my app.
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: textColor,
.backgroundColor: textBackgroundColor,
.shadow: shadow,
.strokeColor: strokeColor,
.strokeWidth: strokeWidth
]
let attributedQuote = NSAttributedString(string: text, attributes: attributes)
let textGenerationFilter = CIFilter(name: "CIAttributedTextImageGenerator")!
textGenerationFilter.setValue(attributedQuote, forKey: "inputText")
if #available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, *) {
textGenerationFilter.setValue(CGFloat(0), forKey: "inputPadding")
}
textGenerationFilter.setValue(NSNumber(value: Double(4.0)), forKey: "inputScaleFactor")
guard let textImage = textGenerationFilter.outputImage else {
return nil
}
It was working perfectly until the release of iOS 16. In iOS 16 the behavior changed and now when I try to render a multiline text, the generated text image ends up being way too wide. With each additional line in the text, the width of generated image drastically increases for some reason. The height increases too, but that is obviously to fit the new text line but the width increases without any obvious reasons.
I attached screenshots of what is happening:
As can been seen, the single liners work perfectly. But with each additional line of text, the rendered image shrinks because the rendered image width becomes wider and wider and it has to shrink to follow the .aspectFit rule.
In the end, the width of the generated image becomes even more than its height, despite the text being clearly bigger in height than width.
That never happens on iOS 15 and the result is as expected there. So is there anything new I am missing either in CIAttributedTextImageGenerator or NSAttributedString introduced in iOS 16 that I am missing?
I have been reading through the various options on how to set the vertical alignment on an NSTextField. I want the text to be displayed in the center and to do it programatically in Swift. Here are the things I have looked so far:
http://www.cocoabuilder.com/archive/cocoa/174994-repositioning-an-nstextfieldcell.html
https://red-sweater.com/blog/148/what-a-difference-a-cell-makes
Vertically Centre Text in NSSecureTextField with subclassing
Get NSTextField contents to scale
vertically align text in a CATextLayer?
One thing I have tried in Swift is to set the following property:
textField.usesSingleLineMode = true
Any tips on the best way to vertically center text would be much appreciated!
This is very hard to do, as Apple makes this very difficult. I achieved it by subclassing NSTextFieldCell and overriding the drawingRectForBounds: method like so:
override func drawingRectForBounds(theRect: NSRect) -> NSRect {
let newRect = NSRect(x: 0, y: (theRect.size.height - 22) / 2, width: theRect.size.width, height: 22)
return super.drawingRectForBounds(newRect)
}
This is just my way to do it, I'm sure there are better ways, which I don't know (yet). And this only works for the standard font size in TextFields (which gives a text height of 22). That's why I hardcoded that. Haven't figured out yet, how to get the height in the cell if you change the font.
Result:
Try this on a playground, it centers the text perfectly, use it on your projects! Hope it helps!
import Cocoa
let cell = NSTableCellView()
cell.frame = NSRect(x: 0, y: 0, width: 100, height: 100)
let tf = NSTextField()
tf.frame = cell.frame
tf.stringValue = "MyTextfield"
tf.alignment = .Center
let stringHeight: CGFloat = tf.attributedStringValue.size().height
let frame = tf.frame
var titleRect: NSRect = tf.cell!.titleRectForBounds(frame)
titleRect.size.height = stringHeight + ( stringHeight - (tf.font!.ascender + tf.font!.descender ) )
titleRect.origin.y = frame.size.height / 2 - tf.lastBaselineOffsetFromBottom - tf.font!.xHeight / 2
tf.frame = titleRect
cell.addSubview(tf)
I have added the NSTextField inside a NSView and centered it.
Another solution was (in an iOS project) to create a UILabel and allow it adjust its size (sizeToFit()) and again embed it inside a UIView.
I personally don't like the calculations in previous answers and the second solution for iOS works for all texts size and row numbers.
I was also facing vertical alignment issue with NSTextField. My requirement involved, rendering a single-line string inside a NSTextField. Additionally,
textfield needed to be resize implying we had programatically resized the font-point-size of the text inside text-field on resize. In this scenario we faced vertical-alignment issues - the mis-alignment was tough to grasp/understand in a straight forward way.
What finally worked:
So, in my scenario a simple,
turn off the "Single Line Mode" in interface builder
for the text-field solved the issue.
The accepted answer works perfectly and here's the Swift3 version.
class VerticallyAlignedTextFieldCell: NSTextFieldCell {
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let newRect = NSRect(x: 0, y: (rect.size.height - 22) / 2, width: rect.size.width, height: 22)
return super.drawingRect(forBounds: newRect)
}
}
Strikethrough is not being displayed, but underline does. The code is as below, its fairly straight forward. When I comment out the underline the text is displayed without a strikethrough, when I comment out the strikethrough and display the underline it works fine. I've tried everything — I must be missing something obvious, the docs say strikeout should work.
I'm running macOS 10.13.6 and Xcode 10.1.
import AppKit
import PlaygroundSupport
class CustomView: NSView {
override func draw(_ dirtyRect: NSRect) {
let attrString = NSAttributedString(
string: "Hello",
attributes: [
//NSAttributedString.Key.underlineStyle: NSUnderlineStyle.thick.rawValue,
NSAttributedString.Key.strikethroughStyle: NSUnderlineStyle.thick.rawValue
]
)
let line = CTLineCreateWithAttributedString(attrString)
// Set text position and draw the line into the graphics context.
let context = (NSGraphicsContext.current?.cgContext)!
context.translateBy(x: 10, y: 10)
CTLineDraw(line, context)
}
}
let customView = CustomView(frame: NSRect(x: 0, y: 0, width: 400, height: 400))
PlaygroundPage.current.liveView = customView
Playground settings. To run this in a Xcode Playground just be sure to change the platform to macOS in the inspector settings (all new playgrounds are set to iOS by default).
CoreText does not have native support for the strikethrough typographic text decoration (but supports underlines just fine as you noted in your question). The full list of string attributes supported by CoreText is described here.
This old, Objective-C based blog post provides the necessary details to implement strikethrough manually using CoreText APIs. Unfortunately, not a quick fix at all.
The higher-level TextKit framework does provide strikethrough support though. To quickly see that working just replace this line in your original code:
CTLineDraw(line, context)
with this instead:
attrString.draw(at: .zero)
And, by the way, that somehow helps validate that there was nothing wrong with your original NSAttributedString to begin with ;)
Of course, depending on how much your code relies on CoreText, switching over to TextKit might be a non-trivial task, so you need to keep that in mind as well.
I have a Label with custom font Unica One-Regular. Since, it is not available in bold font, I am getting problem to make it bold. Is there any way to make it bold?. Here is the font link.
https://fonts.google.com/specimen/Unica+One?selection.family=Unica+One
Thanks in advance.
Sorry I’m on my iPhone but,
Please check the exact name of the font on Xcode, but this code should answer your question :
let attrs:[String:AnyObject] = [NSFontAttributeName: UIFont(name: "Unica One-Bold", size: 12)!]
let boldString = NSMutableAttributedString(string: "text", attributes:attrs)
let lbl = UILabel()
lbl.attributedText = boldString
There is a more recent answer to this question at Is it possible to increase the boldness of the font if the bold font file is not available?.
Short summary: if you have a list of font attributes, you can add a negative stroke width. For example (in Swift):
import AppKit
guard let chosenFont = NSFont(name: "VTypewriter Underwood", size: 24) else {
print("No such font")
exit(1)
}
var fontAttributes = [
NSAttributedString.Key.font:chosenFont,
NSAttributedString.Key.strokeWidth:NSNumber(value:-3.0)
]
let fakelyBoldedText = NSAttributedString(string:"Hello World", attributes:fontAttributes)
This is not optimal; it isn’t really bolding. But with low values for the stroke width (in my experiments, from about -3 to -5, possibly relative to the font width), it does look bolded.
I'm currently working on an iOS application in Swift, and I need to achieve the text effect shown in the attached picture.
I have a label which displays some text wrote by the user, and I need to make the text background corners (not the label background) rounded.
Is there a way to do that?
I'd search the web and Stackoverflow but with no luck.
Thank you.
Here is some code that could help you. The result I got is quite similar to what you want.
class MyTextView: UITextView {
let textViewPadding: CGFloat = 7.0
override func draw(_ rect: CGRect) {
self.layoutManager.enumerateLineFragments(forGlyphRange: NSMakeRange(0, self.text.count)) { (rect, usedRect, textContainer, glyphRange, Bool) in
let rect = CGRect(x: usedRect.origin.x, y: usedRect.origin.y + self.textViewPadding, width: usedRect.size.width, height: usedRect.size.height*1.2)
let rectanglePath = UIBezierPath(roundedRect: rect, cornerRadius: 3)
UIColor.red.setFill()
rectanglePath.fill()
}
}
}
Xavier solution works great.
If you want update background in real-time, use: textView.setNeedsDisplay()