CIAttributedTextImageGenerator - multiline text image generation problem in iOS 16 - swift

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?

Related

SCNText ignores NSAttributedString attributes

Although the docs claim that SceneKit's SCNText works with NSAttributedString, the following NSAttributedString attributes seem to be ignored completely (and perhaps others, too):
shadow
strokeColor
strokeWidth
foregroundColor
SCNText does obey NSAttributedString's font attribute, however -- and it does display on screen.
What I'm trying to do is add a stroke to my SCNText. I do not want to use a SpriteKit overlay for such a simple use case.
From the docs:
When you create a text geometry from an attributed string, SceneKit styles the text according to the attributes in the string, and the properties of the SCNText object determine the default style for portions of the string that have no style attributes.
Here's what I'm doing in code. This code results in a white-filled text in the correct font, but without shadow or stroke of any kind:
let labelNode = SCNNode()
let label = SCNText(string: "\(targetLevel)", extrusionDepth: 0.0)
let font = UIFont(name: "Whatever", size: 4.0)
let shadow = NSShadow()
shadow.shadowBlurRadius = 1.0
shadow.shadowColor = UIColor.black
let attStr = NSAttributedString(string: "\(targetLevel)", attributes: [.font: font!, .shadow: shadow, .strokeColor: UIColor.black, .strokeWidth: 0.5])
label.string = attStr
label.alignmentMode = "kCAAlignmentCenter"
label.flatness = 0.01
//Using a big font and then scaling the node down to make it look less jagged:
labelNode.scale = SCNVector3(0.25,0.25,0.25)
labelNode.geometry = label
labelNode.castsShadow = false
parent.addChildNode(labelNode)
Questions: Am I doing something wrong -- or is this a limitation of SceneKit? Is there any other way to stroke an SCNText? I saw this question, but it's about fill color, not stroke, and didn't fix my problem.
As you've found (and others have noted), you don't automatically get all of the properties of the attributed string rendered in SceneKit. Think of them in terms of what you are drawing (e.g. the shape of the text), rather than how you draw them (e.g. color, shadow etc). All SceneKit uses is the geometry.
If you want outline-stroked 3D text then you may have to get the path from the font and use that for your geometry instead of SCNText. I wrote this category but the code is pretty ancient now, I have no idea if it still works, but it should be enough to get you there.
You could also perhaps render the text as an image and put it as a texture on a billboard, unless you actually want it to be part of the 3D scene. But by this point, a sprite kit overlay isn't looking that complicated, is it?

Swift label not updating frame due change of font size

currently, I am creating a table view that contains an imageView as main UI item inside the cell and on top op that there is a label. Now I'm facing an issue where for long text, the frame of the label is not accurate, and for a long text it is taking a lot of space and it seems like there is a line break but there isn't, this is the issue:
Label issue
This are the properties of the label
Label properties
This is my ViewController and the constraints that I'm using
View Controller on Storyboard
Additional Details
For the Cell, I'm not adding extra code, I just have the IBOutlets and nothing more.
On the CellForRowAtIndexPath, I'm not doing additional code more than setting the image and setting the text.
Thanks
Try to change Content hugging Priority of Label
The font you've selected - Noto Nastaliq Urdu Bold - has very specific type layout properties.
Compare it with the default system font.
Using this code:
let fontDefault = UIFont.systemFont(ofSize: 17.0, weight: .bold)
guard let fontNotoBld = UIFont(name: "NotoNastaliqUrdu-Bold", size: 17) else {
fatalError("Could not create font!")
}
print("fontDefault.lineHeight:\t",fontDefault.lineHeight)
print("fontNotoBld.lineHeight:\t",fontNotoBld.lineHeight)
print()
print("fontDefault.ascender:\t",fontDefault.ascender)
print("fontNotoBld.ascender:\t",fontNotoBld.ascender)
print()
print("fontDefault.descender:\t",fontDefault.descender)
print("fontNotoBld.descender:\t",fontNotoBld.descender)
print()
print("fontDefault.xHeight:\t",fontDefault.xHeight)
print("fontNotoBld.xHeight:\t",fontNotoBld.xHeight)
print()
print("fontDefault.capHeight:\t",fontDefault.capHeight)
print("fontNotoBld.capHeight:\t",fontNotoBld.capHeight)
print()
we get this output in the debug console:
fontDefault.lineHeight: 20.287109375
fontNotoBld.lineHeight: 42.5
fontDefault.ascender: 16.1865234375
fontNotoBld.ascender: 32.368
fontDefault.descender: -4.1005859375
fontNotoBld.descender: -10.132000000000001
fontDefault.xHeight: 9.13916015625
fontNotoBld.xHeight: 9.112
fontDefault.capHeight: 11.97802734375
fontNotoBld.capHeight: 12.138000000000002
I don't know exactly why, but my assumption is that Noto Nastaliq Urdu supports many more language characters which often have very different shapes and heights.
You can either use a more standard font, or design your UI to accommodate the bigger spacing.
Edit
You could try using attributed text with line height settings. However, it will affect the top spacing of the text to a point that it may not suffice.
Here's an example with 3 labels:
top label uses default settings
second label changes maximumLineHeight to 17
third label changes maximumLineHeight to 26
let titleText = "Label with enough text to cause word wrapping onto multiple lines."
guard let fontNotoBold = UIFont(name: "NotoNastaliqUrdu-Bold", size: 17) else {
fatalError("Could not create font!")
}
// without paragraphStyle attribute
var attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
label1.attributedText = attrString
// with paragraphStyle max line height: 17
let ps2 = NSMutableParagraphStyle()
ps2.maximumLineHeight = 17
attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value:ps2, range:NSMakeRange(0, attrString.length))
label2.attributedText = attrString
// with paragraphStyle max line height: 26
let ps3 = NSMutableParagraphStyle()
ps3.maximumLineHeight = 26
attrString = NSMutableAttributedString(string: titleText)
attrString.addAttribute(NSAttributedString.Key.font, value: fontNotoBold, range:NSMakeRange(0, attrString.length))
attrString.addAttribute(NSAttributedString.Key.paragraphStyle, value:ps3, range:NSMakeRange(0, attrString.length))
label3.attributedText = attrString
Here's the result:
With a little more experimentation, such as adding a newline ("\n") at the beginning, you might be able to get it to a satisfactory appearance.

NSTextAttachment image in attributed text with foreground colour

When I add an image attachment to an UITextView with a foreground colour set, the image is blanked out with the set colour:
let attrString = NSMutableAttributedString(string: rawText, attributes: [.font: UIFont.systemFont(ofSize: 17), .foregroundColor: UIColor.black])
let attachment = NSTextAttachment(image: image)
let imgStr = NSMutableAttributedString(attachment: attachment)
attrString.append(imgStr)
textview.attributedText = attrString
When I removed .foregroundColor: UIColor.black, the image is displayed correctly, but I need to be able to set the attributed text colour.
I tried to explicitly remove the .foregroundColor attribute after adding the image attachment with no luck. I also tried to remove the .foregroundColor attribute from most of the text and it still wouldn't work, only removing the attribute from the entire string works:
attrString.removeAttribute(.foregroundColor, range: NSRange(location: attrString.length-1, length: 1)) // does not work
// -------
attrString.removeAttribute(.foregroundColor, range: NSRange(location: 1, length: attrString.length-1)) // does not work
// -------
attrString.removeAttribute(.foregroundColor, range: NSRange(location: 0, length: attrString.length)) // works but no text colour
This is developed on Xcode 11.0, iOS 13. Is this a UITextView/iOS bug or an expected behaviour (which, I don't think is likely)? How do I display the image correctly with the text colour set?
It looks like there is a bug with the NSTextAttachment(image:) constructor (on iOS 13, at the time of this answer), the following image attachment construction works correctly:
let attachment = NSTextAttachment()
attachment.image = image

Blurry font rendering on non-retina Macs when using NSAttributedString drawing

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').

Attributed string cutting and displaying single word into two lines on UILabel

I'm creating attributed text using following attributes,
func attributedString(font: UIFont, contentColor: UIColor, alignment: NSTextAlignment) -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 0.6
paragraphStyle.lineHeightMultiple = 0.8
paragraphStyle.alignment = alignment
paragraphStyle.lineBreakMode = .byWordWrapping
let lineSpacingAttribute: [NSAttributedStringKey: Any] = [NSAttributedStringKey.paragraphStyle: paragraphStyle, NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: contentColor]
let attributedString = NSAttributedString(string: self, attributes: lineSpacingAttribute)
return attributedString
}
I'm displaying this text on UILabel inside a custom tableViewCell. But, it is cutting a single word into two (cutting last letter of a word and displaying it on next line). I've set the numberOfLines to zero, and preferredMaxLayoutWidth for label. And I'm using a custom font.
This problem is happening on small screens only, iPhone SE and iPhone 5S simulators. But, on other simulators it is displaying correctly. Could you please help me to figure out what is wrong in this?
Thanks!
From Apple doc:
var preferredMaxLayoutWidth: CGFloat
This property affects the size of the label when layout constraints are applied to it. During layout, if the text extends beyond the width specified by this property, the additional text flows to one or more new lines, increasing the height of the label.
Depending on your text you can use the adjustsFontSizeToFitWidth property with the minimumScaleFactor property