How to cast a UIFont object to CTFont in Swift - uikit

I'm trying to port some code to Swift that uses UIFont and CTFont, and that (successfully, in Objective-C) uses simple bridged casts to get from one to the other and vice versa.
For example, consider this code (in a UIFontDescriptor category):
UIFont *font = [UIFont fontWithDescriptor:self size:0.0];
NSArray *features = CFBridgingRelease(CTFontCopyFeatures((__bridge CTFontRef)font));
I haven't yet been able to figure out how to express this in Swift in a way that will actually compile. The following at least doesn't:
let font = UIFont(descriptor:self, size: 0.0)
let features = CTFontCopyFeatures(font as CTFont)
Error: 'UIFont' is not convertible to 'CTFont'

Try this. You can't just coerce the values from one type to another. If you create a CTFont from the descriptor and size though, it seems to give you a valid array even if you don't include a matrix for transformations (the nil parameter)
let font = CTFontCreateWithFontDescriptor(descriptor, 0.0, nil)
let features: NSArray = CTFontCopyFeatures(font)
As for creating the CTFontDescriptor, I'd use CTFontDescriptorCreateWithNameAndSize or CTFontDescriptorCreateWithAttributes depending on what you're originally given. The latter takes a simple NSDictionary, and the former just uses a font name and size.
To go from an existing font (call it originalFont) just do the following to get a descriptor:
let font = CTFontCreateWithName(originalFont.fontName as CFStringRef,
originalFont.pointSize as CGFloat, nil)

This has apparently been fixed in a later version of Swift and/or CoreText. At least it works now in my testing using Xcode 7.0.1.

Related

Cocoa NSFont 'leading' attribute build error

In an AudioUnit plugin, I'm using NSFont.
NSFontManager* fontManager = [NSFontManager sharedFontManager];
NSFont* nativefont = [fontManager fontWithFamily:[NSString stringWithCString: fontFamilyName.c_str() encoding: NSUTF8StringEncoding ] traits:fontTraits weight:5 size:fontSize ];
NSMutableParagraphStyle* style = [[NSMutableParagraphStyle alloc] init];
[style setAlignment : NSTextAlignmentLeft];
NSMutableDictionary* native2 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
nativefont, NSFontAttributeName,
style, NSParagraphStyleAttributeName,
nil];
// .. later
void someFunction(NSMutableDictionary* native2)
{
float lineGap = [native2[NSFontAttributeName] leading];
Compiler says (about last line): assigning to 'float' from incompatible type 'NSCollectionLayoutSpacing * _Nullable'
NOTE: This has only failed recently since switching to Xcode 11.1, on an older version of XCode it built OK. Any help appreciated.
In your code, the expression native2[NSFontAttributeName] is of unknown type, and therefore of type id. The compiler will let you send an object of type id any message without complaint, but it has no context for determining the type of the message's return value.
You want to get the leading property of NSFont, but the compiler is just picking any leading property selector at random and I'm guessing it has ended up choosing the leading property of NSCollectionLayoutEdgeSpacing which has a return type of NSCollectionLayoutSpacing not float.
I suspect that casting the expression [(NSFont*)(native2[NSFontAttributeName]) leading] would do the trick, but if I were writing this code I'd simply refer to the original (typed) object, since you already have it:
float lineGap = nativefont.leading;

NSMutableParagraphStyle ignores NSWritingDirectionNatural, is defaulting to LTR for arabic text

I'm using the NSAttributedString UIKit Additions to draw an attributed string in a UIView. The problem I have is that despite using a value of NSWritingDirectionNatural for the baseWritingDirection property of my paragraph style, text always defaults to left-to-right.
Here's how I form the attributed string (simplified):
NSString *arabic = #"العاصمة الليبية لتأمينها تنفيذا لقرار المؤتمر الوطني العام. يأتي ذلك بعدما أعلن اللواء الليبي المتقاعد خليفة حفتر أنه طلب من المجلس الأعلى للقض الدولة حتى الانتخابات النيابية القادمة";
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.baseWritingDirection = NSWritingDirectionNatural;
paragraph.lineBreakMode = NSLineBreakByWordWrapping;
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[NSParagraphStyleAttributeName] = paragraph;
NSAttributedString *string = [[NSAttributedString alloc]
initWithString:arabic
attributes:attributes];
And here's how I draw the text:
- (void)drawRect:(CGRect)rect {
[self.attributedText drawWithRect:rect
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
}
And yet it still flows from left to right:
What am I missing?
I don't believe the writing direction will be automatically set for you using baseWritingDirection unless you switch languages on the device:
"If you specify NSWritingDirectionNaturalDirection, the receiver resolves the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, depending on the direction for the user’s language preference setting."
For some reason the text you have still doesn't seem to work even with arabic selected without adding the language to your supported localizations. This character seemed to work without doing that for me: كتب
Also, it looks like Xcode reverses the characters in hardcoded arabic strings so that may be screwing with some of this copy and paste.
You can use agiletortoises's suggestion or NSLinguisticTagger's Language tag scheme to manually set the language.
I can't explain why it does not work the way you have it written, but I've been using a solution to explicitly set the direction based on known RTL languages, which used this as a starting point:
https://stackoverflow.com/a/16309559
No idea if this is related or not, but when I tested the right-to-left support in Auto Layout, it didn't work until I added a localization for that language (Arabic, Hebrew, etc) to the app.

Calling -[CALayer setBorderColor:] with an RGB color

Hell again all. I seem to be unable to assign RGBA colors to the setBorderColor method of a layer.
I tried:
UIColor *myColor = [UIColor colorWithRed:51.0f/255.0f green:102.0f/255.0f blue:153.0f/255.0f alpha:1.0f];
[l setBorderColor:myColor];
Where l is of type CALayer and I get the warning: Incompatible pointer types sending 'UIColor*' to parameter of type 'CGColorRed ('aka 'struct CGColor *').
Do you know what the reason may be? The warning appears on the last line.
On the internet I find this code over and over again, so I thought it should be valid... Thanks!
CALayer.borderColor is defined as
#property CGColorRef borderColor;
Note that the type here is CGColorRef. You're trying to pass a UIColor*, which is a different beast. Luckily, UIColor has a property that returns a CGColorRef. Try using
[l setBorderColor:myColor.CGColor];

UIKit's [NSString sizeWithFont:constrainedToSize:] in AppKit

Is there any equivalent method in AppKit (for Cocoa on Mac OS X) that does the same thing as UIKit's [NSString sizeWithFont:constrainedToSize:]?
If not, how could I go about getting the amount of space needed to render a particular string constrained to a width/height?
Update: Below is a snippet of code I'm using that I expect would produce the results I'm after.
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize: [NSFont smallSystemFontSize]], NSFontAttributeName,
[NSParagraphStyle defaultParagraphStyle], NSParagraphStyleAttributeName,
nil];
NSSize size = NSMakeSize(200.0, MAXFLOAT);
NSRect bounds;
bounds = [#"This is a really really really really really really really long string that won't fit on one line"
boundingRectWithSize: size
options: NSStringDrawingUsesFontLeading
attributes: attributes];
NSLog(#"height: %02f, width: %02f", bounds.size.height, bounds.size.width);
I would expect that the output width would be 200 and the height would be something greater than the height of a single line, however it produces:
height: 14.000000, width: 466.619141
Thanks!
Try this one:
bounds = [value boundingRectWithSize:size options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin attributes:attributes];
The newer NSExtendedStringDrawing category API (methods with the NSStringDrawingOptions argument) behaves in the single line mode. If you want to measure/render in multi line mode, want to specify NSStringDrawingUsesLineFragmentOrigin.
EDIT: You should be able to do things the normal way in Lion and later. The problems described below have been fixed.
There is no way to accurately measure text among the current Mac OS X APIs.
There are several APIs that promise to work but don't. That's one of them; Core Text's function for this purpose is another. Some methods return results that are close but wrong; some return results that seem to mean nothing at all. I haven't filed any bugs on these yet, but when I do, I'll edit the Radar numbers into this answer. You should file a bug as well, and include your code in a small sample project.
[Apparently I have already filed the Core Text one: 8666756. It's closed as a duplicate of an unspecified other bug. For Cocoa, I filed 9022238 about the method Dave suggested, and will file two NSLayoutManager bugs tomorrow.]
This is the closest solution I've found.
If you want to constrain the string to a certain size, you use -[NSString boundingRectWithSize:options:attributes:]. The .size of the returned NSRect is the size you're looking for.
Here is a more complete example of how you can do this using boundingRectWithSize.
// get the text field
NSTextField* field = (NSTextField*)view;
// create the new font for the text field
NSFont* newFont = [NSFont fontWithName:#"Trebuchet MS" size:11.0];
// set the font on the text field
[field setFont:newFont];
// calculate the size the textfield needs to be in order to display the text properly
NSString* text = field.stringValue;
NSInteger maxWidth = field.frame.size.width;
NSInteger maxHeight = 20000;
CGSize constraint = CGSizeMake(maxWidth, maxHeight);
NSDictionary* attrs = [NSDictionary dictionaryWithObjectsAndKeys:NSFontAttributeName,newFont, nil];
NSRect newBounds = [text boundingRectWithSize:constraint
options:NSLineBreakByWordWrapping | NSStringDrawingUsesLineFragmentOrigin
attributes:attrs];
// set the size of the text field to the calculated size
field.frame = NSMakeRect(field.frame.origin.x, field.frame.origin.y, field.frame.size.width, newBounds.size.height);
Of course, for additional info, take a look at the Apple documentation:
Options for the attributes dictionary
boundingRectWithSize
If you search the documentation for NSString, you will find the "NSString Application Kit Additions Reference", which is pretty much analogous to the UIKit counterpart.
-[NSString sizeWithAttributes:]
is the method you are looking for.

How to handle unknown UILabel height and its effect on labels underneath in Interface Builder?

I have two UILabels in a XIB file, one on top of the other, the contents of which are loaded from a JSON source file on the net.
Given that I'm unsure of the number of lines the labels will take up, and hence the height, how best should I position these relative to each other.
I know in some Java GUI frameworks, one can use various container elements, and in HTML this flow of layout would be the default, but I can't find anything that would seem to do the trick.
Can I do this in Interface Builder, or does this have to be done programmatically?
Thanks for your help..
Edit:
I now have the answer, although it may not be perfect. I have two labels, titleLabel above descLabel. This is how I achieved it:
titleLabel.text = [data objectForKey:#"title"];
descLabel.text = [data objectForKey:#"description"];
CGSize s;
s.width = descLabel.frame.size.width;
s.height = 10000;
titleLabel.frame = CGRectMake(titleLabel.frame.origin.x,
titleLabel.frame.origin.y,
titleLabel.frame.size.width,
[[data objectForKey:#"title"] sizeWithFont:titleLabel.font constrainedToSize: s lineBreakMode:titleLabel.lineBreakMode].height
);
descLabel.frame = CGRectMake(descLabel.frame.origin.x,
titleLabel.frame.origin.y + [[data objectForKey:#"title"] sizeWithFont:titleLabel.font constrainedToSize: s lineBreakMode:titleLabel.lineBreakMode].height + 10,
descLabel.frame.size.width,
[[data objectForKey:#"description"] sizeWithFont:descLabel.font constrainedToSize: s lineBreakMode:descLabel.lineBreakMode].height
);
weffew
You’ll have to do this programatically. The NSString method -sizeWithFont:constrainedToSize:lineBreakMode: (which is actually in a UIKit category) will be of particular use for this.

Resources