From what I learned a general rule of thumb setting values in init is a matter of using the ivars directly.
For example
#interface CustomClass
#property (nonatomic, strong) NSString *name;
#end
and then:
- (instancetype)initWithName:(NSString *)name
{
if (self = [super init]) {
_name = name;
}
return self;
}
Now so far so good. I'm interested in a slightly different case. Let's say you're subclassing a UIView and in the initialiser you want to assign a background color to that subclass. Here, the property backgroundColor is defined in the parent class. My question is: Is it bad style or potentially wrong to use self in the initialiser? Would it be better to set the background color somewhere else?
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor greenColor];
}
return self;
}
I believe it is perfectly fine what you are doing there. At that point, after calling super.init, the self exists, and you can use it (you are calling return self, too, so why would other references to self be wrong?).
Related
I have created a custom class extending UIView. This class has some methods such as Drawerect...
Up to now, I was just putting it in my storyboard and telling that it belongs to the class. I would now allocate and place those objects dynamically. Is there a method so I could call :
[[MyObj alloc] initWithFrame:....]
Id be glad to find any help !
You can create your own constructor in the header file of your class.
The return value is of type id , in its declaration in the main file you need to call a super initialization (for example self = [super initWithFrame:CGRect]) and then return the self. You can customize the parameters of your constructor in the header file to fit your needs.
Example for UIView:
.h:
#import <UIKit/UIKit.h>
#interface CustomView : UIView
-(id)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor;
.m:
#import "CustomView.h"
#implementation CustomView
-(id)initWithFrame:(CGRect)frame backgroundColor:(UIColor *)backgroundColor{
self = [super initWithFrame:frame];
if (self) {
//after allocation you could set variables:
self.backgroundColor = backgroundColor;
}
return self;
}
#end
When instances of UIView are unarchived from an Interface Builder document, their initWithFrame: method isn't called. Instead, the unarchiver calls initWithCoder:. Ideally you should override both methods, and have them call a common method that provides a shared implementation of the initialization code. That way the views will be initialized correctly whether they're instantiated programmatically, or as a result of being unarchived. Here's an example:
- (id)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])) return nil;
[self configureSubviews];
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (!(self = [super initWithCoder:aDecoder])) return nil;
[self configureSubviews];
return self;
}
- (void)configureSubviews
{
// Custom configuration code...
}
I'm wondering what the best way is to initialize values that depend on objects in a NIB. For example, let's say I have a UIView that gets a custom cornerRadius and borderColor.
Right now what I do is
#interface MyViewController ()
#property (nonatomic, weak) IBOutlet UIView *roundyView;
#end
#implementation MyViewController
- (id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// (!) Can't assign to roundyView, hasn't been loaded from NIB yet
// ...
}
return self;
}
-(void)viewDidLoad {
// Ahh, NIB loaded, roundyView has a value
self.roundyView.layer.cornerRadius = 5.0f;
self.roundyView.layer.borderColor = [UIColor redColor].CGColor;
}
#end
So far, so good. Next I add a setter, so I can change the border color from elsewhere in the program.
#property (nonatomic, strong) UIColor *roundBorderColor;
And
-(void)setRoundBorderColor:(UIColor*)roundBorderColor {
_roundBorderColor = roundBorderColor;
self.roundyView.layer.borderColor = roundBorderColor.CGColor;
}
The problem is that I usually call that accessor while instantiating the class, but before it is presented. Something like
MyViewController *vc = [[MyViewController alloc] initWithNibName:#"MyViewController"
bundle:nil];
// Setting the color, NIB hasn't loaded though (!)
vc.roundBorderColor = [UIColor yellowColor];
[self presentViewController:vc animated:YES completion:nil];
This doesn't work, since the setter runs before viewDidLoad. So instead I usually put cruft like this:
-(void)viewDidLoad {
// ...
if (_roundBorderColor != nil) {
// The setter was already called somewhere,
// call it again now that we have the NIB
[self setRoundBorderColor:_roundBorderColor];
}
}
Is there a cleaner way to deal with this?
Goto the identity inspector in the Interface Builder (third option from the left in the interface builder view), and for your view - set the "User Defined Runtime Attributes" and add the following:
Keypath: layer.cornerRadius Value: number, 5.
Unfortunately - this doesn't seem to work for the border color, as the UI allows only "Color" but not CGColor.
A simple workaround to keep your code clean from colors would be to set a user defined property to your view controller and set it via your nib.
In your view controller:
#property (nonatomic,strong) UIColor* borderColor;
And then in the interface builder, set the color via User Defined Runtime Attributes to whatever you want.
Then set the color to the layer's border in viewDidLoad.
Involves some code and not just UI Builder, but at least there is no need to specify the color in the code.
Or, if you just want to set the color using code and don't want to wait for "viewDidLoad" you can do something like:
-(void)setRoundBorderColor:(UIColor*)roundBorderColor {
[self view]; // force view load from nib
self.roundyView.layer.borderColor = roundBorderColor.CGColor;
}
This problem is elegantly solved starting in Xcode 6 by using the IBInspectable feature. For example, in a UIButton subclass:
#IBInspectable var borderColor : UIColor? {
get {
let cg = self.layer.borderColor
return cg == nil ? nil : UIColor(CGColor: cg!)
}
set {
self.layer.borderColor = newValue?.CGColor ?? nil
}
}
An instance of this button in the storyboard / xib now shows a "borderColor" color pop-up menu in the Attributes inspector.
This sets a UIColor, but our property is just a facade for the layer's borderColor and translates between UIColor and CGColor.
You can make it a little cleaner by initialising your property in initWithNibName - that way you can safely access the property in viewDidLoad without the check.
-(id)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_roundBorderColor = [UIColor redColor];
}
return self;
}
-(void)viewDidLoad {
// ...
[self setRoundBorderColor:_roundBorderColor];
}
You should use isViewLoaded method.
- (void)setRoundBorderColor:(UIColor *)roundBorderColor {
_roundBorderColor = roundBorderColor;
// would be launched only if view is initialised
if ([self isViewLoaded]) {
self.roundyView.layer.borderColor = roundBorderColor.CGColor;
}
}
- (void)setBorderRagius:... // same idea as before
- (void)viewDidLoad {
self.roundyView.layer.cornerRadius = self.cornerRadius;
self.roundyView.layer.borderColor = self.borderColor;
}
And you need to set some default values to cornerRadius and borderColor properties. And do nothing if nil value is going to be set.
I'm having trouble wrapping my thoughts about class inheritance. I'm suppsed to create a dashboard like interface in a app, and I'll have maybe 10 widgets/dashlets on that dashboard view. All those dashlets/widgets will have basically same look, with a title on the top, borders, row of buttons on the top and a graph.
Let's say I create a subclass of UI View called 'Dashlet' with properties and outlets, and create XIB file with proper layout and connected outlets etc.
Now I want to create several subclasses of that 'Dashlet' view that will only process data differently, and draw different graphs. My current code looks something like this:
Dashlet.h
#interface Dashlet : UIView{
#private
UILabel *title;
UIView *controls;
UIView *graph;
}
#property (weak, nonatomic) IBOutlet UILabel *title;
#property (weak, nonatomic) IBOutlet UIView *controls;
#property (weak, nonatomic) IBOutlet UIView *graph;
-(Dashlet*)initWithParams:(NSMutableDictionary *)params;
-(void)someDummyMethod;
#end
And in Dashlet.m
- (id) init {
self = [super init];
//Basic empty init...
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(id)initWithParams:(NSMutableDictionary *)params
{
self = [super init];
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:#"Dashlet" owner:nil options:nil] lastObject];
//some init code
}
return self;
}
Now let's say that I create a subclass called CustomDashlet.h:
#interface CustomDashlet : Dashlet
#property (nonatomic, strong) NSString* test;
-(void)testMethod;
-(void)someDummyMethod;
#end
and CustomDashlet.m
-(id)init{
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
-(id)initWithParams:(NSMutableDictionary *)parameters
{
self = [super initWithParams:parameters];
if (self) {
//do some stuff
}
return self;
}
This, kind of works, but I need to override some of the methods declared in the superclass or even add some of my own. Whenever i try to do something like this in CustomDashlet.m
[self someDummyMethod] or even [self testMethod] I get an exception error like this:
NSInvalidArgumentException', reason: '-[Dashlet testMethod]: unrecognized selector sent to instance
Am I even doing this right? Did I miss something? Am I supposed to make this work in some other way? If anyone has suggestions, please feel free to share your thoughts, thank you for all the help.
The problem is that
SalesDashlet *sales = [[SalesDashlet alloc] initWithParams:nil];
does not return a SalesDashlet instance, as expected, but a Dashlet instance.
Here is what happens:
[SalesDashlet alloc] allocates an instance of SalesDashlet.
The subclass implementation of initWithParams: is called with this instance,
and calls self = [super initWithParams:parameters].
The superclass implementation of initWithParams discards self and
overwrites it with a new instance loaded from the Nib file. This is an instance
of Dashlet.
This new instance is returned.
Therefore SalesDashlet *sales is "only" a Dashlet, and calling any subclass
method on it throws an "unknown selector" exception.
You cannot change the type of objects loaded in the Nib file. You could create a second
Nib file containing a SalesDashlet object. If the main purpose of the subclass is
to add additional methods, then the easiest solution would be to add these methods
in a Category of the Dashlet class.
If the problem is with the
- (Dashlet *)initWithParams:
method it is because the base class declares it with a Dashlet return value, whereas the subclass is redeclaring it with a SalesDashlet return instance.
Always use instancetype as the return type for any init method.
I believe you simply need to change following line in your Dashlet.h file:
-(Dashlet*)initWithParams:(NSMutableDictionary *)params;
to following:
-(id)initWithParams:(NSMutableDictionary *)params;
or better:
-(instancetype)initWithParams:(NSMutableDictionary *)params;
You need to change your init methods.
-(Dashlet*)initWithParams:(NSMutableDictionary *)params
-(SalesDashlet*)initWithParams:(NSMutableDictionary *)parameters
The return type on both of these should be id.
The problem you're running into is similar to trying to do this:
NSMutableArray *someArray = [[NSArray alloc] init];
Despite declaring someArray as an NSMutableArray, you've initialized it as an NSArray, and as such, someArray will actually be an immutable NSArray.
So because your SalesDashlet init method calls its super init method and the super explicitly returns an object of type Dashlet, then the SalesDashlet will also return an object of type Dashlet, so you're trying to call testMethod (a method that only exists in SalesDashlet) on an object of type Dashlet (which doesn't know about the testMethod method).
Changing your return type to id will make the methods return an object of the right type.
As a note, you've done your init, and initWithFrame methods correctly.
SalesDashlet *mySalesDashlet = [[SalesDashlet alloc] initWithFrame:someFrame];
Creating a SalesDashlet in this way will allow you to call [mySalesDashlet testMethod].
Your initWithFrame has return type of id in both super and sub classes.
I have an abstract interface in Objective-C where every sub-class needs to set up a property and then do the exact same thing with that property at the end of init. I'm trying to avoid duplicated code with something like this:
Interface File
#interface Shape : NSObject
#property (nonatomic) PropertyType *prop;
- (id)init;
- (void)initProperty;
#end
Implementation File
#implementation Shape
- (id)init
{
if(self = [super init]) {
[self initProperty];
[prop doSomething];
}
return self;
}
- (void)initProperty
{
}
#end
My problem is that every sub-class will need a different set of parameters passed to initProperty in order to implement the method correctly:
#implementation Rectangle
- (void)initPropertyWithRect:(CGRect)rect
{
prop = [RectangleStuff rectangleWithRect:rect];
}
#end
#implementation Circle
- (void)initPropertyWithRadius:(CGFloat)radius
{
prop = [CircleStuff circleWithRadius:radius];
}
#end
Is there a clean way to do what I'm trying to do in Objective-C? So far, my options seem to be:
Create a "property bag", and just pass around an NSDictionary.
Duplicate the [property doSomething]; code in every subclass.
Somehow pass in a factory object to init, and have the factory object create prop. This approach seems the cleanest, but I'd need the factory object to keep the rect and/or radius as internal state somehow, and that doesn't seem clean to me.
Any thoughts?
I would probably choose #2 (to keep it simple). If the property is only set once
(in the subclass init method), you could override the property setter method in the
superclass, and do the additional stuff there.
Untested code:
- (void)setProp:(PropertyType *)prop
{
_prop = prop; // (Assuming ARC)
[_prop doSomething];
}
First, I feel obligated to mention that your init function should not do anything besides initialize the object. That said, every rule has a time and a place to be broken, so I'll offer what suggestions I can.
Your init function is no different than any other function. You can do things before and after you call super. While generally discouraged, this would be a good place to do it. Your init in your subclass would now look like this:
- (id)init
{
self.myProperty = value;
self = [super init];
if (self) {
// more init stuff
}
return self;
}
I ended up using a variant of what was suggested in the other two answers:
Shape.h
#interface Shape : NSObject
#property (nonatomic) PropertyType *prop;
- (id)initWithProperty:(PropertyType *prop);
#end
Shape.m
#implementation Shape
- (id)initWithProperty:(PropertyType *)prop
{
if(self = [super init]) {
_prop = prop;
[_prop doSomething];
}
return self;
}
#end
Rectangle.m/Circle.m
#implementation Rectangle
- (void)initWithRect:(CGRect)rect
{
return [self initWithProperty:[RectangleStuff rectangleWithRect:rect]];
}
#end
#implementation Circle
- (void)initWithRadius:(CGFloat)radius
{
return [self initWithProperty:[CircleStuff circleWithRadius:radius]];
}
#end
I have an ivar which is mentioned in my header
#interface MyClass : UIView{
int thistone;}
- (IBAction)toneButton:(UIButton *)sender;
#property int thistone;
#end
and I have synthesized it in the implementation:
#implementation MyClass
#synthesize thistone;
- (IBAction)toneButton:(UIButton *)sender {
if(thistone<4)
{thistone=1000;} // I hate this line.
else{thistone=thistone+1; }
}
I cannot find (or find in any manual) a way to set a nonzero initial value. I want it to start at 1000 and increase by 1 each time I press the button. The code does exactly what I intend, but I'm guessing there's a more proper way to do it that saves me the if/else statement above. Code fixes or pointers to specific lines in online documentation greatly appreciated.
Every object has a variant of the init method called at instantiation. Implement this method to do such setup. UIView in particular have initWithFrame: and initWithCoder. Best to override all and call a separate method to perform required setup.
For example:
- (void)commonSetup
{
thisTone = 1000;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonSetup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super initWithCoder:coder])
{
[self commonSetup];
}
return self;
}