So in my code, I was checking if my characters fit in my Label or not and had the following line :
return self.adjustsLetterSpacingToFitWidth;
This was placed in an implementation of UILabel. Can someone tell me just what is the exact alternative for this? The documentation says - Use NSKernAttributeName, but I wasn't quite able to understand that. Can someone help me on this?
In the larger sense - The method is called as:
xLab.adjustLetterSpacingToFitWidth = YES;
In my code, I have:
#implementation UILabel ()
- (BOOL) getter {
return self.adjustsLetterSpacingToFitWidth;
}
- (void) setter:(BOOL) setVal {
self.adjustsLetterSpacingToFitWidth = setVal;
}
#end
First of all, your getter and setter are entirely superfluous as shown; wherever you call getter and/or setter, you could simply get/set adjustsLetterSpacingToFitWidth directly.
As for the question of how to do auto-kerning with NSKernAttributeName, Apple's documentation says: “To turn on auto-kerning in the label, set NSKernAttributeName of the string to [NSNull null]”, i.e., you would do something like:
NSMutableAttributedString *s;
s = [[NSMutableAttributedString alloc] initWithString:my_uilabel.text];
[s addAttribute:NSKernAttributeName
value:[NSNull null]
range:NSMakeRange(0, s.length)];
my_uilabel.attributedText = s;
But if you did not want to do automatic adjustment of letter spacing but rather find out whether the text fits in the label or not, you might want to check the various methods in NSString UIKit additions. (This guess of intent is based on the wording in the original question.)
Related
Is it possible to subclass UIFont and initialize it with another one (or a font descriptor)? The problem is, I can't call super.init(descriptor:size:) for initialization because it's a convenience initializer. The purpose of doing this is to change the attributes for ascender and descender (override the read-only properties) due to the fact that I only display numbers and there's too much whitespace above and below the letters (which I draw directly in a graphics context). When there is another elegant solution, it would be very welcome.
I appreciate your help, thanks in advance.
What you're asking for breaks encapsulation, and perhaps doesn't make sense either.
Rather than using inheritance, and with a few assumptions, a tidier way of achieving this would be to add a category to UIFont and provide your own convenience factory (e.g. class) method.
#interface UIFont (MyUIFont)
#property (assign) CGFloat ascender;
#property (assign) CGFloat descender;
+(instancetype) UIFontWithLessSpacing;
#end
#implementation UIFont (MyUIFont)
+(instancetype) UIFontWithLessSpacing
{
UIFont *font = [UIFontWIthName:#"MyFont" size:12.0f];
font.ascender = 0.0;
font.descender = 0.0;
return font;
}
There are caveats:
That UIFont actually responds to selectors setAscender and setDescender - they are hidden on the public interface
That setting these properties has an effect on layout and that conversely it doesn't have unwanted side effects.
You're poking around in a black-box with no guarantees that what you do will work, or will continue to work.
Currently all my buttons and textfields have attributedText values defined to be attributed strings.
Consider the case with a simple UILabel. Whenever I have to change the text for this UILabel (based on some user action), I have to redefine the attributes on the NSAttributedString. One way is to simply create a subroutine that generates these attributes whenever I require them but that's a concern given there could be a number of different labels (or attributed strings) that would require such convenience methods.
Another could be simply changing the text field and having observers add those attributes but that's the same amount of work and now probably more complicated.
Is there a simple way of achieving the above without redefining attributes?
Exploring #Harry's ideas, here are a few ideas :
Category on NSAttributedString, category on UILabel, or category on NSDictionary, and maybe a mix of them, according to which one best suits you and your project.
Using a category on NSAttributedString in priority before UILabel could be more interesting in case you wanted to use the custom NSAttributedString for others kind of object (like a UITextView).
A good start:
typedef enum : NSUInteger {
AttributeStyle1,
AttributeStyle2,
} AttributeStyles;
A possible category method on NSDictionary:
-(NSDictionary *)attributesForStyle:(AttributeStyles)style
{
NSDictionary *attributes;
switch(style)
{
case AttributeStyle1:
attributes = #{}//Set it
break;
case AttributeStyle2:
attributes = #{}//Set it
break;
default:
attributes = #{}//Set it
break;
}
return attributes;
}
Category possible on UILabel:
-(void)setString:(NSString *)string withAttributes:(NSDictionary *)attributes
{
[self setAttributedText:[[NSAttributedString alloc] initWithString:string attributes:attributes];
}
Category possible on NSAttributedString:
-(NSAttributedString *)initWithString:(NSString *)string withStyle:(AttributedStyles)style
{
//Here, a mix is possible using the first method, or doing here the switch case
//Ex: return [[NSAttributedString alloc] initWithString:string attributes:[NSDictionary attributesForStyle:style];
//And to use like this: [yourLabel setAttributedText:[[NSAttributedString alloc] initWithString:string withStyle:AttributeStyle1];
}
I want my app to be when you click the stepper it will resize the font in a UITextView. The problem is I'm getting errors.
- (IBAction)myStepper:(id)sender {
[myStepper setMinimumValue:14.0]
self.myStepper.maximumValue =20.0;
UIFont newSize = [myTextView fontWithSize:self.stepper.value];
self.myTextView.font = newSize;
}
This all of my code, am I missing something?
You are missing a self and a semicolon in the first line:
[self.myStepper setMinimumValue:14.0];
And an asterisk and self in this line, and stepper should be myStepper:
UIFont *newSize = [self.myTextView fontWithSize:self.myStepper.value];
I would clean your method a bit like this:
- (IBAction)myStepperValueChanged:(UIStepper *)sender {
[sender setMinimumValue:14.0];
[sender setMaximumValue:20.0];
UIFont * newSize = [UIFont fontWithName:myTextView.font.fontName size:sender.value];
[self.myTextView setFont:newSize];
}
but I'd put the first two lines into the -viewDidLoad method as part of the standard init procedure, if they are static values in runtime using the outlet's name (I assume it is myStepper in your class):
[myStepper setMinimumValue:14.0];
[myStepper setMaximumValue:20.0];
or I'd set these values in the Interface Builder, and you'd not need to deal with any outlet for your stepper at all.
NOTE: it is hard to tell which solution would fit better for you, you have not shared too much information which could help me to recommend a specific solution.
For easier code-reading, you shouldn't give your action method the same name of your UISteppert property. Change it to -(IBAction)stepperValueChanged:(id)sender for example.
You don't need to set the min and max value in this method, but in your initialization methods
- (void) viewDidLoad
{
[super viewDidLoad];
// Various implementation
self.myStepper.minimumValue = 14;
self.myStepper.maximumValue = 20.0;
}
- (IBAction) stepperValueChanged:(id)sender
{
self.myTextView.font = [UIFont fontWithSize:self.myStepper.value];
}
It looks like you are some issues understanting the differences between method and properties, you should read some documentation and tutorials if you're beginning development (and welcome to the development world ;) ).
EDIT :
The property myStepper doesn't exists in your class. If you use interface builder, you have to link your stepper to an UIStepper property : this apple tutorial will help you : https://developer.apple.com/library/ios/recipes/xcode_help-interface_builder/articles-connections_bindings/CreatingOutlet.html .
Otherwise, if you don't use Interface Builder, you must add an UIStepper property to your class and allocate it by yourself.
I have a NSTextAttachment subclass with overridden attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex: imageForBounds:textContainer:characterIndex:.
I need to redraw the attachment(s) at some point. Calling setNeedsDisplay on the UITextView doesn't work.
Any ideas? I'd like to avoid recreating attachments and/or the attributed string.
You'll want to use one of textView.layoutManager's methods.
invalidateDisplayCharacterRange:
imageForBounds:textContainer:characterIndex: will be called again.
attachmentBoundsForTextContainer:[...]Index: will not be called again.
Good if the image has been changed with another one of the same size.
invalidateLayoutForCharacterRange:actualCharacterRange:
imageForBounds:textContainer:characterIndex: will be called again.
attachmentBoundsForTextContainer:[...]Index: will be called again.
Good if the image has been changed with another one of a different size.
If you just want to update a single attachment, you may find this helper method I wrote helpful:
- (NSRange)rangeOfAttachment:(NSTextAttachment *)attachment {
__block NSRange ret;
[self.textStorage enumerateAttribute:NSAttachmentAttributeName
inRange:NSMakeRange(0, self.textStorage.length)
options:0
usingBlock:^(id value, NSRange range, BOOL *stop) {
if (attachment == value) {
ret = range;
*stop = YES;
}
}];
return ret;
}
You can pass the resulting NSRange of this method to the first argument of either of those invalidate methods. For the actualCharacterRange: argument of the second method, I've been passing in NULL without any problems.
Well, after some digging, that can done by invalidating the layout manager:
[textView.layoutManager invalidate....]
Apple docs
If you just want to change the NSTextAttachment which is init by a image, I suggest you to use
setAttachmentSize:size forGlyphRange:range
I want a button that is similar to this:
ONE
TWO
Where one word is over the other. However, it may be:
THREE
FOUR
Or any number, really. Is there a way with NSAttributedStrings that I can say to always have a line-break after the first word?
It's really not about NSAttributedString or NSString, but rather about the button itself. You may think of subclassing UIButton and overriding setTitle:forState: to automatically replace the first space with a \n.
Specifically the setTitle:forState: would look something like this
- (void)setTitle:(NSString *)title forState:(UIControlState)state {
NSRange firstSpaceRanger = [title rangeOfString:#" "];
if (firstSpaceRanger.location != NSNotFound) {
title = [title stringByReplacingCharactersInRange:firstSpaceRanger withString:#"\n"];
}
[super setTitle:title forState:state];
}
For instance, given one two three this will produce
one
two three
In order to make the button multiline you can do, in the UIButton initializer:
self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
Yes, firstSpaceRanger is intentional. I couldn't resist.