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...
}
Related
I'm a little about this. I'm initializing a UITableViewController subclass CTSettingsVC via:
CTSettingsVC *settingsVC = [[CTSettingsVC alloc] init];
However, the initWithStyle initializer is being called. This is my m file:
#interface CTSettingsVC ()
#end
#implementation CTSettingsVC
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
NSLog(#"Why is this called?")
self.title = #"Settings";
}
return self;
}
#end
I'm not sure if this is the intended behaviour or if I'm missing something.
This happens because initWithStyle: is the designated initializer of UITableViewController. The init method of UITableViewController looks something like this:
- (instancetype)init
{
//NOTE: self is being used instead of super
return [self initWithStyle:UITableViewStylePlain];
}
Not all UIKit classes follow this convention of overriding init with default values for the designated initializer when one exists. I often do it myself if I plan on using init for a default initializer to ensure my subclasses are not broken in future versions of UIKit.
init and initWithStyle: are convenience initializers for UITableViewController. Internally they look something like this:
- (instancetype)init
{
return [self initWithStyle:UITableViewStylePlain];
}
- (instancetype)initWithStyle:(UITableViewStyle)style
{
self = [self initWithNibName:nil bundle:nil];
if (self) {
_tableViewStyle = style; // Private, used in loadView.
}
return self;
}
The exception where you will not see initWithNibName:bundle: called is when your view controller is instantiated by a storyboard. There, initWithCoder: is the initializer.
I'm trying to create a custom control based on UIScrollView.
The control should get the information necessary through its dataSource object (same behaviour as UITableView or UIPickerView).
The problem is that I'm trying to define the dataSource through Interface Builder and not by code. (ex. you can set a UITableView dataSource to File's owner in a .xib file)
Here's my new control header:
#import <UIKit/UIKit.h>
#protocol HorizontalPickerDataSource;
#interface HorizontalPicker : UIScrollView
#property (nonatomic,assign) IBOutlet id <HorizontalPickerDataSource> dataSource;
#end
#protocol HorizontalPickerDataSource
- (NSInteger)numberOfColumnsInHorizontalPicker:(HorizontalPicker *)horizontalPicker;
- (UIView *)horizontalPicker:(HorizontalPicker *)horizontalPicker viewForColumn:(NSInteger)column;
#end
Pretty straightforward.
The UIViewController that adopts the HorizontalPicker should be its dataSource.
I'm defining it through Interface Builder since I set the dataSource an IBOutlet.
I override initWtihCoder and logged the dataSource there (after creating the object of course) and it appears to be nil.
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self){
NSLog(#"dataSource: %#",self.dataSource);
[self setup];
}
return self;
}
the setup method whole logic based on dataSource not being nil.
Why is dataSource nil and how can I fix it?
Thank you for your time (:
Edit #1
Here's my implementation of NSCoding, what's wrong with it?
- (id)initWithCoder:(NSCoder *)aDecoder
{
id dataSource = [aDecoder decodeObjectForKey:#"dataSource"];
self = [super initWithCoder:aDecoder];
if(self){
self.dataSource = dataSource;
NSLog(#"dataSource: %#",self.dataSource);
[self setup];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[super encodeWithCoder:aCoder];
[aCoder encodeObject:self.dataSource forKey:#"dataSource"];
}
You have to completely implement NSCoding for this to work. In your case, your initWithCoder: is not decoding the datasource. You need to do something like this:
- (id)initWithCoder:(NSCoder *)aDecoder {
id ds = [decoder decodeObjectForKey:#"dataSource"];
if(self = [self initWithDataSource:ds){
NSLog(#"dataSource: %#",self.dataSource);
[self setup];
}
return self;
}
You should also provide the companion method in NSCoding, encodeWithCoder:.
I have a procedure that I'll need in a lot (if not all) of my view controllers. I want to know how I can put it in one place (for code cleanliness and maintenance) and utilize it elsewhere.
There are more ways on how to approach this - depending on what exactly you would like to achieve.
If this methods are tied with UIViewController's life and data you would probably want to subclass UIViewController or make an UIViewController category.
A: Subclassing (you want to add some custom properties, variables, methods or you want to override a method):
MySubclassedViewController.h
#import <UIKit/UIKit.h>
#interface MySubclassedViewController : UIViewController
#property (copy) NSString *myVerySpecialString;
-(void) myVerySpecialMethod;
#end
MySubclassedViewController.m
#import "MySubclassedViewController.h"
#implementation MySubclassedViewController
-(void) initialization
{
self.myVerySpecialString = #"initialized";
}
- (id)initWithCoder:(NSCoder*)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initialization];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[self initialization];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
[self initialization];
}
return self;
}
-(void) viewDidLoad
{
[super viewDidLoad];
[self myVerySpecialMethod];
}
-(void) myVerySpecialMethod
{
if ([self.myVerySpecialString isEqualToString: #"initialized"])
{
self.backgroundColor = [UIColor yellowColor];
}
}
#end
B: Category (you just want to add some extra method to a class):
UIViewController+SpecialCatrgory.h
#import <UIKit/UIKit.h>
#interface UIViewController (SpecialCategory)
-(void) myVerySpecialMethod;
#end
UIViewController+SpecialCatrgory.m
#import "UIViewController+SpecialCatrgory.h"
#implementation UIViewController (SpecialCategory)
-(void) myVerySpecialMethod
{
self.backgroundColor = [UIColor yellowColor];
}
#end
C: Dedicated helper class
On the other hand if you find yourself using some independent method on more than one place you might
want to consider writing up a helper class and use it as a singleton.
MyHelperClass.h
#interface MyHelperClass : NSObject
+ (instancetype)sharedHelper;
-(NSString *) myVerySpecialMethod;
#end
MyHelperClass.m
#import "MyHelperClass.h"
#implementation MyHelperClass
+ (instancetype)sharedHelper
{
static MyHelperClass *_sharedHelper = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedHelper = [[MAConnectionClient alloc] init];
});
return _sharedHelper;
}
-(NSString *) myVerySpecialMethod
{
return #"a result from your very special method";
}
#end
You use it simply by importing MyHelperClass.h (or putting it in -Prefix.pch), without explicitly creating an instance. For example:
NSString *someString = [[MyHelperClass sharedHelper] myVerySpecialMethod];
There are many ways to achieve this.
Create a base viewcontroller and add your procedure. Subclass this in all your view controller.
Create a common utility class and add your procedure and make it as a class method.
Add your procedure in .pch file.
We have many ways to do it but in general create one global class, import it in YourProjectName-Prefix.pch file.
We can also go for another way i.e create any class method and you can call it anywhere through it's Class Name.
One example, you might have seen many times in your code-
:
In appDelegate.h file, if we make this method and implement it in appDelegate.m file then
+ (NSString *)applicationDocumentDir
{
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
then we can access it from anywhere in the code like :
NSString *strTemp = [AppDelegate applicationDocumentDir];
To know more better way have a look here.
Use of a singleton for creating a basic style helper class
If you're not sure that the method will be used in all Controllers, I'd recommend creating a category for the functionality you're adding. You can find a good intro to categories here.
If you're sure you'll need it in all UIViewControllers, creating a base Controller and subclassing should be the better approach.
All other methods of implementing (Placing a class method in a utility / Adding to *-Prefix.pch) will work, but might not be ideal solutions (assuming the functionality your're adding is only applicable to UIViewController).
My super class defines a private method called "commonInit" which is only called at construction.
The super class is derived by 2 additional classes, each of which also implement a method called "commonInit"
While constructing the objects of the derived class I see in the debugger that the subclass method is called from the scope of the superclass.
This seems to be very dangerous behavior - even in a trivial case when by coincedence you "overwrite" your superclass private method
How can I overcome this behavior without renaming the method in the super class?
Example:
#interface ASuperView : UIView
#end
#implementation ASuperView
-(id)init
{
self = [super init];
if(self)
{
[self commonInit]; // BOOM - The derived view method is called in this scope
}
return self;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self commonInit];
}
return self;
}
-(void)commonInit
{
//setup the view
}
#end
#interface ADerivedView : ASuperView
#end
#implementation ADerivedView
-(id)init
{
self = [super init];
if(self)
{
[self commonInit];
}
return self;
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self commonInit];
}
return self;
}
-(void)commonInit
{
//setup the view for the derived view
}
#end
In this image PXTextMessageBox derived from PXTextBox
Both declare privately the method common init
There is no such thing as 'private' methods in obj-c. At best you can hide the existence of a method from consumers of your header, but by design anyone that has a reference to your object can call any method it implements - even if they don't have that method defined in the header. Your best bet will be to define a new method, say _private_commonInit, and not share that in your class header.
I believe this is actually by design. Polymorphism at its best even! .. self actually refers to the object that originally sent the message (which is not always the class instance where self appears) ... one way to solve this would be to chain the commonInit in the same way Init is chained ... a call to [super commonInit] will invoke the correct method from the subclass ...
I have a class that has this in the initializer:
#implementation BaseFooClass
-(id) init
{
if (self = [super init])
{
// initialize instance variables that always need to start with this value
}
return self;
}
-(id) initWithSomeInt:(int) someInt
{
if (self = [self init]) // <-- I need to make sure that I am calling BaseFooClass's init here, not SubFooClass's, does that make sense?
{
self.someInt = someInt;
}
return self;
}
#end
That is all fine and dandy. My problem is that when I implement the subclass:
#implementation SubFooClass
-(id) init
{
return [self initWithSomeInt:0];
}
-(id) initWithSomeInt:(int) someInt
{
if (self = [super init]) // <--- Infinite loop (stack overflow :) )
{
// initialize other variables
}
}
#end
I basically need to specifically call the BaseFooClass's init rather than the SubFooClass's init.
I cannot change the way the objects are initialized, as I am converting a project from C# to use in my iPad application.
Thank you all in advance
EDIT:
Due to someone asking, here is my header:
#interface BaseFooClass : NSObject
// implicit from NSObject
// -(id) init;
-(id) initWithSomeInt:(int) someInt;
// more methods
#end
#interface SubFooClass : BaseFooClass
// implicit from NSObject
// -(id) init;
// implicit from BaseFooClass
//-(id) initWithSomeInt:(int) someInt;
#end
Objective-C doesn't work this way because of the way the runtime converts methods into function calls. Self is always an instance of the allocated class, even when invoking the super-class's methods. You need to create your designated initializer for your BaseClassFoo and always go there. So you should be doing something like this:
#implementation BaseFooClass
-(id) init
{
return [self initWithSomeInt:0]; // redirect super class's designated initializer
}
-(id) initWithSomeInt:(int) someInt
{
if ((self = [super init])) // Designated initializer always calls into super class's designated initializer (in this case, NSObject's designated initializer is init
{
self.someInt = someInt;
}
return self;
}
#end
#implementation SubFooClass
// Here we don't override init because our super class's designated initializer
// is initWithSomeInt:
// -(id) init
// {
// return [self initWithSomeInt:0];
// }
// we override this because it's our superclass's designated initializer, plus it
// is ours as well
-(id) initWithSomeInt:(int) someInt
{
if ((self = [super initWithSomeInt:someInt]))
{
// initialize other sub-class specific variables
}
}
#end
You have to call [super initWithSomeInt:someInt]; in the init method of your SubFooClass.
;)
ADDED:
I think is weird you try to call init within iniWithSomeInt . The usual thing would be to call [super initWithSomeInt:someInt] in initIthSomeInt method of SubFooClass and change what you need inside the if clause.