Subclassing UIFont - ios

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.

Related

How to programmatically add a label in a custom subview?

I'm designing a card game (cards against humanity) for iOS, there are two types of cards, the whites ones, and the black ones. So I've implemented a CardView class which inherits from UIView, and two class, WhiteCardView and BlackCardView which both inherit from CardView.
As you guess, almost only the color will change between those two, but I still want two different view class for those two (even if they are almost the same at the moment). So, I have four questions about that.
EDIT: I mnage to find out some answer by myself, but for the last one, I'm lost.
1.I need advice on my implementation, I don't know if I'm doing it right to set the colors of the cards which is stored in CardView but set in the init of my WhiteCardView. I think I have to override only one init, but I don't know which one. So if someone can confirm or correct what I already did, that would be great. Thanks
Here is my CardView code:
IB_DESIGNABLE
#interface CardView : UIView
-(void)customDrawUI;
-(void)setData;
#property (nonatomic) UIColor* IBInspectable secondaryColor;
#property (nonatomic) UIColor* IBInspectable primaryColor;
#property(nonatomic) UILabel* txtLabel;
#end
#implementation CardView
-(void)customDrawUI{
self.layer.cornerRadius=5.0;
self.backgroundColor = self.primaryColor;
}
-(void)setData{
self.txtLabel.text = #"some text";
}
#end
And the WhiteCardView code:
IB_DESIGNABLE
#interface WhiteCardView : CardView
#end
#implementation WhiteCardView
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
CGContextSetLineWidth(context, 1);
CGRectInset(myFrame, 5, 5);
[self.secondaryColor set];
UIRectFrame(myFrame);
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.primaryColor= [UIColor whiteColor];
self.secondaryColor= [UIColor blackColor];
[super customDrawUI];
[super setData];
}
return self;
}
#end
2.In interface builder (where I correctly define the custom class for each of my cards), my views aren't live rendered, the code in my drawRect method WhiteCardView is executed but I can see no change.
And when I run my app, I get the same result. Just white rectangle, without any rounded angle.
Any idea why there is no changing in my view?
EDIT: I didn't put the IBDESIGNABLE tag in the right place, now it works !
3.Is there any way to execute that line: self.backgroundColor = self.primaryColor; in the parent drawRect method ? Because it's currently in both: White and BlackCardView, and there is certainly a way to do it in the parent class (I'll add common code to those two class, so I need a way to do it).
Maybe it's possible to do this in the init from CardView, but as it's not working at the moment, I can't test it.
EDIT: I find out, the setBackground couldn't be called from drawRect, so I define a new method in the CardView class to do that. And I call it from the init methods.
4.The most important part: how is it possible to add a label on each card (not via interface builder but via the CardView class), and be able to set its content programmatically?
I defined a property for that label, and I set its value but I have no idea how to render it.

Custom Labels Implementation and Use for iOS Apps

I use Storyboards heavily for my iPhone development. To keep the Labels look the same I create custom UILabels and set the font and size in the custom classes. Then in the Storyboard I assign those classes to the labels displayed in the view.
This works fine but I have 4-5 different kind of labels which only differ by size or weight. How do I deal with this situation? Currently I have the following:
PrimaryLabel
PrimaryLabelBold
DescriptionLabel
DescriptionLabelSmall
DescriptionLabelBold
I think this is too much work and these have to be a better way!!
No need to subclass UILabel so many times. Just create one subclass, like so:
MyLabel.h
typedef NS_ENUM(NSUInteger, MyLabelStyle) {
MyLabelStyleSmall,
MyLabelStyleMedium,
MyLabelStyleBig,
};
#interface MyLabel : UILabel
#property (nonatomic) MyLabelStyle style;
#end
MyLabel.m
#import "UILabel+Styles.h"
#implementation UILabel (Styles)
- (void)setStyle:(MyLabelStyle)style
{
switch (style) {
case MyLabelStyleSmall:
self.font = [UIFont systemFontOfSize:12.0];
break;
case MyLabelStyleMedium:
self.font = [UIFont systemFontOfSize:17.0];
break;
case MyLabelStyleBig:
self.font = [UIFont systemFontOfSize:22.0];
break;
default:
self.font = [UIFont systemFontOfSize:17.0];
break;
}
}
#end
In your storyboard, set the style of a particular label using User Defined Runtime Attributes:
2 corresponds to MyLabelStyleBig. Use strings instead of an enum if you want.
You can subclass the UILabel class and than create the constructor method in which you can define the type of label you want ie
PrimaryLabel
PrimaryLabelBold
DescriptionLabel
DescriptionLabelSmall
DescriptionLabelBold
For this you can create an enumeration depecting the types of Label's you want. Within the constructor method you can set this enum value.
Happy coding :)

Font Size Resizes by Stepper

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.

UILabel - Alternative for Deprecated Method "adjustsLetterSpacingToFitWidth"

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

UIControl Lifecycle

Is there a documented Lifecycle for a UIControl somewhere?
Here's why I ask:
Suppose I have a UITextField. I could easily wire up a button that changes the border color like so:
myTextField.layer.borderColor = [[UIColor redColor] CGColor];
Now suppose I have a custom control that's a subclass of UIControl. That same code will not change the border color unless I also issue setNeedsLayout, like so:
[myControl setNeedsLayout];
Is there an event method somewhere that I need to implement to make this work without the setNeedsLayout?
For future generations, here's how I solved the problem on my own.
In my .h file:
#property (nonatomic, strong, setter = setBorderColor:) UIColor *borderColor;
In my .m file:
- (void)setBorderColor:(UIColor *)clr {
borderColor = clr;
myControl.layer.borderColor = borderColor.CGColor;
}
Works like a charm.

Resources