Set delegate object for custom class - ios

I made a custom view.
I set two starting methods,
1. initWithFrame (for code initialization)
2. initWithCoder (for storyboard initialization)
In my custom class
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// [self setUp] method contain the code to run the delegate.
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// [self setUp] method contain the code to run the delegate.
[self setUp];
self.backgroundColor = [UIColor greenColor];
}
return self;
}
Then, I add a View to storyboard and change the class at identity inspector.
I connect the view to the viewcontroller property.
I set the delegate and data source
In my view controller
#interface ViewController () <UIControlViewDataSource, UIControlViewDelegate>
#property (weak, nonatomic) IBOutlet UIControlView *controlView;
#end
- (void)viewDidLoad
{
self.controlView.delegate = self;
self.controlView.dataSource = self;
}
The initWithCoder is running before viewDidLoad,
Because of it the delegate will never run because the initWithCoder in my custom class is running before I set the delegate in viewDidLoad in my view controller.
And the delegate property will have NULL value.
How could I set the delegate?

If you are instantiating these objects from a xib or storyboard (which it looks like you are based on the IBOutlet, you should set your delegate in -(void)awakeFromNib
At this point all the outlets will have been set.
You shouldn't have to depend on the order in which objects get instantiated. You should let them all get instantiated, the outlets set and then do whatever you need to do with your delegate

Your problem is not getting the delegate set, but rather that your object requires a delegate for construction. A delegate should be an optional property.
Whatever operation your UIView requires its delegate for is likely something that can be pushed out of the actual constructor. If you want to have the delegate set automatically during the nib loading process, you could declare it as an IBOutlet property in your view. It will still be set after it is initialized though.
Without getting more detail about why you need to call the delegate, it's impossible to say where you could defer that logic to.

Related

Where is better to keep the instance of data source object of Table View?

I'd like to have a separate class that conforms to UITableViewDataSource protocol and works with data objects.
Where is better to keep, allocate and initialize the instance of this class?
If I do this in ViewDidLoad method of Table View Controller subclass, that connected to Storyboard, the instance of data source is deallocated after method ViewDidLoad is finished.
- (void)viewDidLoad
{
[super viewDidLoad];
MyTableViewDataSource* myDataSource = [[MyTableViewDataSource alloc] init];
self.tableView.dataSource = myDataSource;
} // self.tableView.dataSource is deallocated
Will it be better solution to create a strong property of data source object in of Table View Controller subclass, that connected to storyboard and than allocate and initialize instance of data source in ViewDidLoad?
#interface MyTableVC : UITableViewController
#property (strong, nonatomic) MyTableViewDataSource *myDataSource;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
self.myDataSource = [[MyTableViewDataSource alloc] init];
self.tableView.dataSource = myDataSource;
}
The reason it's being deallocated is because the UITableView does not retain it:
#property(nonatomic, assign) id< UITableViewDataSource > dataSource
^^^^^^
This is normal with delegates, in order to avoid retain cycles.
Therefore if you want to create a new object to act as your data source, you will need to retain it, as per your 2nd code snippet.

iOS Subclassing a Custom Class

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.

Custom init method on ViewController to be called before viewDidLoad

I have a custom container view controller that I instantiate from a storyboard and that has a bunch of methods that modify the content of subviews that I've set outlets to from the storyboard.
There are a bunch of ways that I might instantiate this ViewController, and at present I have to make sure that, however I instantiate it, I either display it, explicitly call loadView, or access its .view property before I start doing anything that uses its outlets (since they're all null pointers until loadView is called).
Ideally, I'd like to put a call to loadView or .view in a single initialiser method of my ViewController to get around this problem, rather than having to put the call to .view in a bunch of different places where I initialise the ViewController from.
Does the UIViewController class have a designated initialiser? If not, what methods do I need to modify with my custom initialisation logic to ensure that it will be called on initialisation of my ViewController no matter what?
awakeFromNib seems to be a suitable place for your purpose. From the documentation:
During the instantiation process, each object in the archive is
unarchived and then initialized with the method befitting its type.
Objects that conform to the NSCoding protocol (including all
subclasses of UIView and UIViewController) are initialized using their
initWithCoder: method.
...
After all objects
have been instantiated and initialized, the nib-loading code
reestablishes the outlet and action connections for all of those
objects. It then calls the awakeFromNib method of the objects.
You can override these to cover the init cases:
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
[self customInit];
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self customInit];
}
return self;
}
- (id)init
{
self = [super init];
if (self) {
[self customInit];
}
return self;
}
- (void) customInit
{
//custom init code
}
However this is not good practice and you should do your subview manipulation in viewDidLoad.

viewDidLoad for UIView?

What is the viewDidLoad for UIView?
I have a UIView with xib. I would like to hide one of it's subviews when it is loaded.
I tried to use this.
- (id)initWithCoder:(NSCoder *)aDecoder{
....
_theView.hidden = YES;
}
But the subview _theView is nil at this point.
This answer didn't help me, becouse at moment of creating the UIViewController, the UIView is not created yet. It is created programaticly, later on.
Try
-awakeFromNib method
Or in xib set the view property hidden for your subview
AwakeFromNib is called only if the view loaded from nib file.
layoutSubviews is called for all views, you can add bool _loaded = yes; in the layoutSubviews function and know if the view loaded.
The accepted answer is misleading.
awakeFromNib will always be called, not just if a nib is used.
From the apple docs:
awakeFromNib:
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
Link
In the next example I've used only a storyBoard
You can test this very easily.
This is our ViewController:
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
}
-(void)awakeFromNib
{
NSLog(#"awakeFromNib in view controller");
}
#end
RedView.m:
#import "RedView.h"
#implementation RedView
-(void)awakeFromNib
{
NSLog(#"awakeFromNib inside RedView");
self.green.hidden = YES;
}
#end
Order of print:
awakeFromNib in view controller
awakeFromNib inside RedView
viewDidLoad
And of course the green view will be hidden.
Edit:
awakeFromNib won't be called if you use only code to create your view but you can call it yourself or better yet, create your own method.
Example without a StoryBoard (only code):
RedView.m:
#import "RedView.h"
#implementation RedView
-(void)loadRedView
{
NSLog(#"loadRedView");
self.green = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
self.green.backgroundColor = [UIColor greenColor];
[self addSubview:self.green];
self.green.hidden = YES;
}
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.red = [[RedView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
self.red.backgroundColor = [UIColor redColor];
[self.view addSubview:self.red];
[self.red loadRedView];
}
#end
There is no such method in general. The question is, where is your _theView coming from.
If your view, including its subview, is loaded from the same nib/xib/storyboard then you can use awakeFromNib which will be called after the complete object hierarchy has been loaded from the archive, so your _theView should be set as well.
If your view is created programmatically but does not create the subview for _theView itself, that means there has to be a place in your code where you add that subview. In that case you have two options
Either hide _theView from the caller after you added it
Or declare a prepareForDisplay method (or similar) on your view class and call that after your view has been created and _theView has been assigned. In that prepareForDisplay (or whatever name you choose) method you can do whatever you like, e.g. hide _theView.
I would not recommend to abuse layoutSubviews for this as it is meant for a different purpose and will be called several times during the lifetime of a view, not just once as you want it to be. Yes you can save whether it was called before, but I would consider that a hack as well. Better create your own method to initialize the view in a way you want after you set it up correctly and call that.
layoutSubviews will be call for all the views you can set you view as hidden there instead of awakeFromNib.
If you are using xib then you can set the default hidden property.
private var layoutSubviewsCounter = 0
override func layoutSubviews() {
super.layoutSubviews()
if layoutSubviewsCounter == 0 {
layoutSubviewsCounter += 1
viewDidLoad()
}
}
func viewDidLoad() {
// your code here
}

iOS UIView subclass with NIB file does not display the UI designed in IB

I have a class that inherits from UIView, and this class has some controls that I have placed on it in IB.
Then, in the NIB file for my main view controller, I placed a view, and changed the class to my subclass, and created an outlet for the subclass. However, when I run my application, the app does not display the UI that I put on the subclass, it is just blank.
I am getting the initWithCoder and awakeFromNib messages in the subclass, here is what the subclass .m file basically looks like:
#import "AnalyticsDetailView.h"
#implementation AnalyticsDetailView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"AnalyticsDetailView" owner:self options:nil];
[self addSubview:[v objectAtIndex:0]];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
#end
I am not sure if the initWithFrame is correct, but since that method is not firing, I suspect that it doesn't matter at this point. If I put a breakpoint in my app after I have seen the subclass methods fire, I can look at the outlet subclass and the frame is the same as what I have created in IB.
Anyone have any suggestions (missing code, bad IB connections, etc.) on what to look for that I have missed or am doing incorrectly? Thanks.
To get your interface to appear, you'll need to explicitly instantiate a AnalyticsDetailView from your parent view controller.
So in somewhere like the viewDidLoad: or viewWillAppear: methods, you'll add a line that says:
AnalyticsDetailView * newView = [[AnalyticsDetailView alloc] initWithFrame: CGRectMake(x,y,height,width)];
[parentView addSubview: newView];
[newView release]; // subview retains for us

Resources