I have a simple request that I have spent much time on (embarrassingly)..
I have sub-classed a UITableView to add some functionality. These new features require things like NSMutableSet which require allocation/initialization.
I have put my object's initialization routine in
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
which I understood from the apple docs to correct - but this doesn't get called (determined by break-pointing on the code).
I am using IB, and have dragged a UITableView onto the view, and changed it's class to my new sub-class. There is no UITableViewController.
I have also tried:
- (void)loadView {
- (id)init {
- (id)initWithFrame:(CGRect)frame {
with no success. I would like to have this class work both with IB, and programmatically in the future. Everything works apart from the location of this initialization..
When objects load from a XIB file, they get -(id)initWithCoder:(NSCoder*)coder.
If you create objects from XIBs and programmatically, you'll need to implement both the designated initializer -initWithFrame:style: and -initWithCoder:, doing all your init stuff in each one.
Keeping those two in sync can be a pain, so most folks like to break the init stuff out into a private method, typically called -commonInit.
You can see an example of this in action in some of the Apple sample code: HeadsUpUI.
- (void)commonInit
{
self.userInteractionEnabled = YES;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self commonInit];
}
return self;
}
One common mistake that people make when they're new to Cocoa or Cocoa Touch, is to subclass when they don't actually need to. I've seen many examples of custom windows, tableviews, scrollviews and imageviews that need never have been written.
What functionality are you adding to UITableView? Are you sure that what you want to do can't be accomplished through the delegate methods, or by using a custom cell class?
Related
I am subclassing an UIButton which is going to be present in all of my app's ViewControllers, kinda Navigation Button. I would like just to put it to my VC and apply custom class, without any code in ViewController itself. So, the questions:
1. is it possible?
2. I am using this code now in my UIButton custom class. What is wrong?:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self addTarget:self action:#selector(didTouchButton) forControlEvents:UIControlEventTouchUpInside];
}
return self;
}
- (void)didTouchButton {
NSLog(#"YEAH, it works, baby!");
}
UPD: seems that even initWithFrame method is not being called at all.
Loading from the nib I think.The initWithFrame method doesn't work if not called programatically.
Try -awakeFromNib Method
See this question
I made a test app to understand how exactly init methods work. In my simple UIViewController I call the following:
- (id)init {
self = [super init];
self.propertyArray = [NSArray new];
NSLog(#"init called");
return self;
}
The above does not print any values in NSLog. However, when I write :
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
NSLog(#"init called");
self.propertyArray = [NSArray new];
return self;
}
It does print "init called" in console. So my question is: why is the init method called and the other is not? Which one do I have to use, when i want to do my stuff before the view loads (and any other methods called)?
Any explanation will be appreciated, thanks.
To begin with, you mention ViewController in your question. A UIViewController's designated initializer is initWithNibName:bundle:
You would never want to override just init on a UIViewController.
There is a lifecycle for each object:
When initializing in code, you have the designated initializer. Which you can find in the documentation for that class. For NSObject derived classes this would be init:
- (id)init
{
self = [super init];
if (self) {
// perform initialization code here
}
return self;
}
All objects that are deserialized using NSKeyUnrchiving, which is what happens in the case of Storyboard's or NIBs(XIBs), get decoded. This process uses the initWithCoder initializer and happens during the unarchiving process:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// perform initialization code here
}
return self;
}
It is common, because of this lifecycle, to create a shared initializer that gets called from each initializer:
- (void)sharedInit
{
// do init stuff here
}
- (id)init
{
self = [super init];
if (self) {
[self sharedInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self sharedInit];
}
return self;
}
To take it one step further. In the case of Storyboard's and XIBs, if you want to perform initialization or updates AFTER the unarchiving is completed and to guarantee all your outlets and actions are connected, you would use awakeFromNib:
- (void)awakeFromNib
{
// do init or other stuff to be done after class is loaded from Interface Builder
}
When a class is instantiated in your code, you pick which initializer to call, depending on your needs. When a class is instantiated through framework code, you need to consult the documentation to find out what initializer would be called.
The reason that you see the behavior that you describe is that your view controller is in a storyboard. According to Cocoa documentation, when a view controller is instantiated through a storyboard, its initWithCoder: initializer is called. In general, this call is performed when an object gets deserialized.
Note that it is common to check the result of self = [super initWithCoder:aDecoder]; assignment, and skip further initialization when self is set to nil.
When you load view controller from nib file (and storyboard) it uses initWithCoder: so in your example this is why it call this method.
If you create your view controller programatically this method won't work and you should override initWithFrame: initialiser instead and also you should create view controller by calling
[[UIViewController alloc] initWithFrame:...];
The different inits are different constructors. As in any other language, an instance is instantiated by the most appropriate constructor. That's initWithCoder: when restoring from an archive.
As a style point, note that use of self.propertyArray in a constructor is considered bad form. Consider what would happen if a subclass overrode setPropertyArray:. You'd be making a method call to an incompletely instantiated object. Instead you should access the instance variable directly, and perform the idiomatic if(self) check to ensure it is safe to do so.
I'm having problems controlling the scrolling within a UITextView that I'm using, so I've opted to create my own subclass.
I've got a very basic question about providing implementations for some of the UIScrollView superclass methods.
Here's my skeleton code for the UITextView subclass:
#interface PastedTextView : UITextView
#end
#implementation PastedTextView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"scrollRectToVisible");
}
- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated
{
NSLog(#"setContentOffset");
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
NSLog(#"zoomToRect");
}
#end
When will those UIScrollView methods be called? Only from my own client code? Or will they be called by the framework?
Update:
The reason I've asked this is because I'm having the following problem: I'm programatically adding text to the UITextView (from the pasteboard). When I do so, if the textview has scrolled such that the top of the content is no longer in view, the text view scrolls back to the top after the new text has been appended.
I'm not explicitly triggering this scroll, so it's happening within the framework.
I haven't found anything in Apple's documentation that describes this behaviour. So, I've been trying to locate the source of the scrolling so that I can avoid it...
When this scroll happens, none of the above methods are called. Incidentally, neither is UITextViews scrollRangeToVisible method (I've tried adding that method to the subclass implementation). I can't figure out why that implicit scroll back to the top is happening and I want to prevent it...
If you override these UIScrollView methods as shown, any caller (be it your code, or the system's) will hit your implementation instead of the builtin UIScrollView one. If you want to take advantage of the system implementation, you can always call super.
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[super zoomToRect:rect animated:animated];
NSLog(#"zoomToRect");
}
I'm developing an iOS app with latest SDK.
I have created a class that inherits from UIView and I have to do some initialization every time the class is instantiated.
I have to call a method called setUpVars: but I don't know where to send a message to that method:
- (id)initWithFrame:(CGRect)frame;
- (id)initWithCoder:(NSCoder*)aDecoder;
This class can be used with a custom xib, or added to a Storyboard, so I need to be sure that that method will be called on every case.
- (void)setUpVars
{
_preferenceKey = #"";
_preferenceStatus = NO;
_isDown = NO;
}
Where do I have to add [self setUpVars];?
Essentially you will be wanting to cover both cases
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self setUpVars];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setUpVars];
}
return self;
}
I think that you need to send this message from each method, also do not forget about awakeFromNib method.
You can create BOOL variable, something like isAlreadySetup and set it to YES in setUpVars method.
Docs Says
awakeFromNib
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
- (void)awakeFromNib
{
[self setUpVars];
}
If you use Interface Builder to design your interface, initWithFrame: is not called when your view objects are subsequently loaded from the nib file. Instead initWithCoder gets called. So you can initialize your variables in both methods if you prefer a generic way. Works in both case
I tend to think you should call this method from the -(void)viewDidLoad method of the controller in charge
In the ViewController's interface, I have
#property int count;
and in the implementation, I have
#synthesize count;
-(id) init {
self = [super init];
if (self) {
self.count = 100;
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%i", self.count++);
}
but for some reason, the first time self.count got printed, it is 0 but not 100?
One of various -init methods will be called on your UIViewController, depending on whether it came out of a .xib, storyboard, or is alloc'd manually somewhere else in your code.
A better place to put this kind of initialization is in -viewDidLoad, something like this
- (void)viewDidLoad {
[super viewDidLoad];
self.count = 100;
}
Put a NSLog or debugging breakpoint in your init method and I suspect you'll find it isn't called. If you look at UIViewController, you'll see other initialization methods (e.g. if you're using a NIB, it would invoke initWithNibName:bundle:). If it's via a storyboard, it can differ. See the discussion of initialization in Apple's View Controller Programming Guide for iOS.
A better place for general view controller configuration is viewDidLoad.
Change it to:
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.count = 100;
}
return self;
}
The view is actually getting created by the XIB, which is 'decoding' it or unboxing it. When this happens, the XIB calls initWithCoder: