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:.
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 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...
}
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 ...
In the app I'm working on, I have a UIViewController sublcass and a UIView subclass. in the storyboard the view controller contains the UIview. in the uiview I'm drawing something but I need it to know some values that it should be getting from the view controller. So I created a custom protocol in the view controller .h file:
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
and in the UIView class I confirmed it as having the protocol above and I implemented its method. However. when I pass a number from the view controller, UIView doesn't receive it. Using NSLog, I figured out that UIView isn't entering - (void)numberOfS:(int)number; am I doing anything wrong? How can I fix it? and is there another way that I can send data from the UIViewController class to the UIView controller?
Here is the full code:
UIViewController.h
#protocol SSGraphViewControllerProtocol <NSObject>
- (void)numberOfSemesters:(int)number;
#end
#interface SSGraphViewController : UIViewController
#property (weak, nonatomic) id <SSGraphViewControllerProtocol> delegate;
#end
UIViewController.m
#implementation SSGraphViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.delegate numberOfSemesters:2];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
UIView.h
#interface SSGraph : UIView <SSGraphViewControllerProtocol>
#end
UIView.m
static int numberOfS = 0;
#implementation SSGraph
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
SSGraphViewController *graph = [[SSGraphViewController alloc] init];
graph.delegate = self;
return self;
}
- (void) numberOfSemesters:(int)number{NSLog(#"YES");
numberOfSemesters= number;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
}
Read This Article, It is best example with Description
http://css.dzone.com/articles/do-not-publishcreating-your
Also read for create Protocol
Following i describe simple Example for How to create protocol
#DetailViewController.h
#import <UIKit/UIKit.h>
#protocol MasterDelegate <NSObject>
-(void) getButtonTitile:(NSString *)btnTitle;
#end
#interface DetailViewController : MasterViewController
#property (nonatomic, assign) id<MasterDelegate> customDelegate;
#DetailViewController.m
if([self.customDelegate respondsToSelector:#selector(getButtonTitile:)])
{
[self.customDelegate getButtonTitile:button.currentTitle];
}
#MasterViewController.m
create obj of DetailViewController
DetailViewController *obj = [[DetailViewController alloc] init];
obj.customDelegate = self;
[self.navigationController pushViewController:reportTypeVC animated:YES];
and add delegate method in MasterViewController.m for get button title.
#pragma mark -
#pragma mark - Custom Delegate Method
-(void) getButtonTitile:(NSString *)btnTitle;
{
NSLog(#"%#", btnTitle);
}
You're creating a view controller instance inside of initWithFrame:, assigning its delegate to be self, and then not keeping a reference to the controller or adding its view into the view hierarchy. This is certainly not what you meant to do. Make the connection in your storyboard instead, by making the delegate property an IBOutlet and connecting them by right clicking on the view controller and dragging from the circle next to the property name onto your view instance.
As an aside I'm not convinced of the utility of using a protocol in this way. If the view needs to know some information to do its job, if should either expose some properties that can be set by the controller, or declare a dataSource protocol and query its dataSource rather than rely on the view controller defining the interface it needs.
// Add an observer to your ViewController for some action in uiview
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveActionNotification:)
name:#"someActionNotification"
object:nil];
// Post Notification and method in your Viewcontroller will be called
[[NSNotificationCenter defaultCenter] postNotificationName:#"someActionNotification" object:self];
// at the end Dont forget to remove Observer.
[[NSNotificationCenter defaultCenter] removeObserver:#"someActionNotification"];
It works if I put it in viewDidLoad but I can't imagine that's the best place to do it. I tried putting it here:
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// HERE
}
return self;
}
But that didn't work. Where should I put it?
In this example I'm talking about creating the NSMutableArray (alloc and initing it) for this class.
You could go with a lazy-loading technique as A-Live suggests in the comments, such that your array will be initialized when you actually need it. The idea is that in a property getter, you first check to see if your array was initialized. If not, initialize then return it.
Example
Note: This is a crude and untested example, and you may need to make
the necessary changes depending on whether or not you use ARC.
MyViewController.h
#interface MyViewController : UITableViewController
{
NSArray *_myArray;
}
#property (nonatomic, readonly) NSArray *myArray;
-(void)doSomething;
#end
MyViewController.m
#interface MyViewController()
-(NSArray *)fetchArrayData;
#end
#implementation MyViewController
#synthesize myArray = _myArray;
#pragma mark - Property Getter
-(NSArray *)myArray
{
if (_myArray==nil)
_myArray = [[self fetchArrayData] retain];
return _myArray;
}
#pragma mark - Cleanup
-(void)dealloc
{
[_myArray release];
[super dealloc];
}
#pragma mark - Instance Methods
-(void)doSomething
{
NSLog(#"myArray: %#", self.myArray);
}
#pragma mark - Private Methods
-(NSArray *)fetchArrayData
{
NSArray *arrayData = [NSArray arrayWithObjects:#"Apples", #"Oranges", nil];
return arrayData;
}
#end
You have 4 initialization methods:
- (id)init
- (id)initWithCoder:(NSCoder *)aDecoder
- (id)initWithStyle:(UITableViewStyle)style
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
It all dependes on how you're instantiating the class.
You can initialize your instance variables in initWithStyle: if you create your controller programmaticaly or in initWithCoder:/awakeFromNib if it's loaded from nib/storyboard.