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.
Related
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?).
I have the following protocol:
#protocol CardTransferFundsDelegate <NSObject>
#property (strong, nonatomic) id<CardTransferFundsView> view;
#end
I have the following in the .h file:
#interface CardTopViewModel : NSObject<CardTransferFundsDelegate>
- (instancetype)initWithVirtualCard:(Card *)card;
#end
In my .m I have the following:
#interface CardTopUpViewModel()
#property (strong, nonatomic) Card *card;
#end
#implementation CardTopUpViewModel
-(instancetype)initWithCard:(Card *)card {
self = [super init];
if(self){
self.card = card;
}
return self;
}
- (id <CardTransferFundsView>)view {
return nil;
}
- (void)setView:(id <CardTransferFundsView>)view {
self.view = view;
}
#end
The issue I have is whenever I call setView it then calls self.view which then calls the getter method and I have a program that just keeps running infinitely.
So my question is, How do I fix this issue, such that the protocol still keeps the property but I am able to get and set the view without having the program run infinitely?
You have implemented the setter, setView, in terms of itself, set.view, so it will recurse until the call stack overflows.
To implement the property specified in your protocol you need three things:
Storage - an instance variable in which to hold the properties value
A setter
A getter
First add an instance variable, say myView:
#implementation CardTopUpViewModel
{
id <CardTransferFundsView>) myView; // storage for view property
}
Now your setter becomes:
- (void)setView:(id <CardTransferFundsView>)view
{
myView = view;
}
And the getter is similarly trivial.
Or of course you can just skip all this and use a property declaration in your #interface which matches that in the protocol and the compiler will provide the storage, setter and getter automagically.
HTH
You should not call self.view = view will call the setter method again and again resulting infinite call. That moto is do not call setter method in with in setter method (setting property will call it again). So Issue in below lines. so view is already a property of viewcontroller. - (void) setView:(id)view is setter method for the view Property. try use another name.
Basically you should be very careful while creating method start with set. Usually try to use method name which starts with any verb as it does some action.
- (void)setView:(id <CardTransferFundsView>)view {
self.view = view;
}
create any property of id <CardTransferFundsView>) with other name and use with in setter method in place of self.view = view.
For a class where I have an actual view attached, I can use viewDidLoad to handle any variables or constants I want to use. It will run before any other code in the method the moment the view becomes in use.
Is there something that does that for classes without views attached? For example, if I have a class called PDFCreator, when I create it like this:
PDFcreator *pdf = [[PDFcreator alloc] init];
Is there a way to run a function at that moment so that I can set those variables? Or some other way to encapsulate the data in that class?
Yeah, simply add any code to the init method:
#implementation PDFCreator
- (instancetype)init
{
self = [super init];
if (self) {
_someInstanceVariable = #"Hello";
_anotherInstanceVariable = 12;
}
return self;
}
This assumes your #interface PDFCreator looks something like this:
#interface PDFCreator : NSObject
#property NSString *someInstanceVariable;
#property (assign) int anotherInstanceVariable;
...
You've identified the answer: override -init and perform the work there. Apple's documentation on object initialization may help you, here.
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've managed to setup a custom UIView class with a nib.
My .h looks like
#interface MyView : UIView <UITextFieldDelegate>
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) MyView *topView;
And .m
#implementation MyView
NSString *_detail;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
MyView *v = [[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if (self.topView == nil)self.topView = self;
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
-(NSString *)topDetail{
return _detail;
}
-(NSString *)detail{
return [self.topView topDetail];
}
-(void)setTopDetail:(NSString *)detail{
_detail = detail;
}
-(void)setDetail:(NSString *)detail{
[self.topView setTopDetail:detail];
}
- (BOOL)textFieldShouldReturn{
//here I show an UIAlertView using self.detail for the message
}
Note: The setup I have works exactly how I want it to.
The problem
What I would like to do is remove my manual detail methods and turn NSString *_detail into #property (...)NSString *detail
When I try it with the #property, then within my ViewController if i call
myView.detail = someString, myView will be referring to the top most view. Then if textFieldShouldReturn gets called because of user interaction, then it calls the nested MyViews _detail which has not been set.
What I want:
To not have to write extra code for access to _detail regardless of where I'm accessing it from. I want to merely declare the property and go on with my usual coding.
Your problem is that you're trying to keep the a class reference, topView, with an object property.
In other words every objects' topView is the object itself, which makes no sense.
Your definition should be:
#interface MyView : UIView <UITextFieldDelegate>
// Class "properties"
+ (instancetype)topview;
+ (void)setTopView:(UIView *)topView;
// Object properties
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) NSString *detail;
Now you can keep track of the topView:
static MyView * _topView;
#implementation MyView
+ (instancetype)topView {return _topView}; // You could also create one here lazily
+ (void)setTopView:(UIView *)topView { _topView = topView };
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
JUITextFieldHint *v = [[[NSBundle mainBundle] loadNibNamed:#"JUITextFieldHint" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if ([MyView topView] == nil)[MyView setTopView:self];
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
No more need for manual setters and getters. Now you can use your detail property, either with anyInstance.detail or [MyView topView].detail, or even MyView.topView.detail if you like dots like me ;)
You're init method still looks weird but should work. Check Apples init template.
Lastly, textField can be weak as long as it has a superview, otherwise make it strong.
My xib contained one UIView (no controller). I had the UIView set to MyView for the class.
I changed the UIView back to just UIView then set File's Owner to MyView. This solved issues of recursion (which is why I had such a weird setup in the first place) and caused my variables and IBOutlets to be linked up properly.
Credit goes to How do I create a custom iOS view class and instantiate multiple copies of it (in IB)? and some of the comments which I missed the first couple times I read through it.