So, I have a view that is being serialized and stored in a file. Within that view, is n subclassed UILabels, with the only difference being a live property. I have the initwithcoder and encodewithcoder within the UILabel subclass, but I am still unable to get the custom variable within the label. I have included my subclass' methods and the below. Any help is appreciated.
Custom UILabel:
- (id) initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self != nil) {
self.live = [decoder decodeBoolForKey:#"live"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeBool:self.live forKey:#"live"];
}
Because I am only unarchiving the view, which contains the labels within, I assume that ios does not unarchive the custom labels?
Thanks
You are missing a call to [super encodeWithCoder:aCoder]. This will result in an incorrectly serialised object, I'm not sure what the consequences are, but you seem to have found one of them!
Related
I want to create a custom class RoundedImageView by subclassing the UIImageView class.
In order to make it round, I use the following code:
self.layer.cornerRadius = self.frame.size.width/2;
self.layer.masksToBounds = YES;
Where should I place this code? In the initializer? Or maybe in layoutSubviews? I know there's not a good idea to access properties from self within the initializer(because self may not be fully initialized), that is why I am confused and I'm not sure where to place this code.
The canonical initialization for a UIView (or any class derived from UIView) looks like this
- (void)setup
{
// do any initialization here
}
- (void)awakeFromNib
{
[self setup];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
[self setup];
return self;
}
This works for views that are created by storyboard and views that are created programmatically. Source: the CS193P videos.
The initialisation method is definitely the right place, since you want to set those properties only once.
About your concern related to acessing properties, the idea is that a property accessor could be overridden in a derived class and thus access portions of the object not already properly initialised. This is not a great concern in your case, I would say, unless you plan to override layer's getter in some not fully sensible ways.
I have a UIViewContoller subclass BaseViewControler. It has a method named -initWithStyle:. If I subclass that class as SubBaseViewContoller,
what should -[SubBaseViewController init] look like?
My code:
- (id)init
{
self = [self initWithStyle:kGreenStyle];
if (self) {
// stuff
}
return self;
}
In SubBaseViewController I don't have initWithStyle:
and my app crashed randomly with the -init above, I checked other view controllers which are subclasses of BaseViewController, and they use self = [super initWithStyle:kGreenStyle], and work. What's the explanation?
A UIViewController has two designated initializers:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle;
and
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
The second form will be invoked when Interface Builder is used to define a View Controller with a resource, that is nib files ore segues. The first form can be used when you manually create a view controller.
You can override these initializers as follows:
// Designated initializer
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
; // your initialization
}
return self;
}
// Designated initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
; // your initialization
}
return self;
}
Note: The method init will not be called when created from a resource. When you invoke init yourself, it will eventually call initWithNibName:bundle: with nil arguments.
See also: Reference: Multiple Initializers and the Designated Initializer
Another possible solution for your problem:
One approach to provide a method to initialize a custom View Controller is to use a "convenience class method". In this case, you don't need to override the designated initializers, instead you provide properties in your subclass.
The convenience class method may then look like as shown below:
// MyViewController
+ (instancetype) myViewControllerWithStyle:(Style)style
{
// Possibly get the bundle and the nib name (or leave it nil)
...
// Invoke a designated initializer with suitable parameters:
MyViewController* viewController = [[MyViewController alloc] initWithName:nibName
bundle:bundle];
viewController.style = style;
return viewController;
}
You should use super instead of self:
- (id)init
{
self = [super initWithStyle:kGreenStyle];
if (self) {
}
return self;
}
If you do it that way you force just parent class to do all the initialisation which it supposed to do. And if you want to do something in init method just in your subclass you add it here:
- (id)init
{
self = [super initWithStyle:kGreenStyle];
if (self) {
// do some stuff you need to do in subclass initialisation
// for example init variable specific just for that class (not parent class)
}
return self;
}
This is kind of a synthesis of Greg's and CouchDeveloper's answers.
But really, it's a defense of Greg's answer.
First, yes, understand designated initializers. They are important, and CouchDeveloper
is right to emphasize this.
Second, nothing prevents a subclass of UIViewController from having
completely different designated initializers (preferably one) of your own choosing.
If you are creating UIViewControllers outside of Interface Builder,
as it appears the OP may be doing, the primacy of -initWithNibName:Bundle:
and -initWithCoder: becomes silly.
Apple say this:
When you define a subclass, you must be able to identify the designated initializer of the superclass and invoke it in your subclass’s designated initializer through a message to super. You must also make sure that inherited initializers are covered in some way.
Given that, here's what the OP's BaseViewController might look like:
const NSUInteger kDefaultStyle = 0; // just to have something to use
#implementation BaseViewContoller
// No longer valid for *this* class.
// Should have one for -initWithCoder: too, but elided for this example.
- (instancetype)initWithNibName:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
// New designated initializer for *this* class.
// This should call the superclass's designated initializer.
- (instancetype)initWithStyle:(NSUInteger)style
{
// use the designated initializer of the superclass to init
if ((self = [super initWithNibName:nil bundle:nil])) {
// stuff
}
return self;
}
// in *other* initializers, you *should* call *your* class's
// designated initializer.
- (instancetype)init
{
return [self initWithStyle:kDefaultStyle];
}
#end
Given this, I think CouchDeveloper's "this is wrong" calling out in the comments
of Greg's answer is itself wrong. This is because:
BaseViewController is unseen, so we can't possibly know what its designated initializer is, regardless of its superclass.
OP has given every indication that -[BaseViewController initWithStyle:] is the designated initializer
OP is perfectly free to declare [SubBaseViewController init] as the designated initializer, so long as he follows the rules.
Finally, unrequested advice for the OP:
Assuming those aren't typos in your original post, you should always capitalize the
first letter of class names. BaseViewController, not baseViewController.
You can swim against the idiom is when you
become a total badass; by that point, I'll wager you'll not want to.
I created a custom UITableViewCell (for this example, let's just say the subclass is MyViewCell) which has an Nib file associated to it MyViewCell.xib. The nib contains a UITableViewCell with one subview UIView (named cardContainer) that's simply a rectangle with a blue background. I want to add a drop shadow around the UIView, so I added set the layer properties in the -initWithCoder call:
#implementation MyViewCell
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initView];
}
return self;
}
- (void) initView
{
UIBezier Path*shadowPath =[UIBezierPath bezierPathWithRect:view.bounds];
[self.cardContainer.layer setShadowColor: [UIColor blackColor].CGColor];
[self.cardContainer.layer setShadowOpacity: 0.8];
[self.cardContainer.layer setShadowRadius:3.0];
[self.cardContainer.layer setShadowOffset: CGSizeMake(2.0,2.0)];
view.layer.shadowPath = shadowPath.CGPath;
}
#end
The problem I'm having is that these layer properties aren't being drawn. If I call the -initView call within awakeFromNib or drawRect it's drawn as expected. My question: why doesn't my original code work? Where should I be calling initView? Is there some view lifecycle? I understand that the initWithCoder doesn't have the outlets connected, but it didn't crash at runtime.
I read through Apple documentation around Views and searched through the SO questions without finding an answer. I found this SO answer, but again doesn't explain.
Hey I found a better way to do this ,just add some runtime attributes for your subview cardContainer
like this
no more code in .m file anymore.
EDIT:
From:NSNibAwaking Protocol
Important: Because the order in which objects are instantiated from an archive is not guaranteed, your initialization methods should not send messages to other objects in the hierarchy. Messages to other objects can be sent safely from within awakeFromNib—by which time it’s assured that all the objects are unarchived and initialized (though not necessarily awakened, of course).
You need to add this,
self.cardContainer.layer.masksToBounds = NO;
I'm trying to save custom cell objects and later on show them. But fail to understand the mechanism totally i have:
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.action = [decoder decodeObjectForKey:#"action"];
}
return self;
}
and
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
}
return self;
}
this way I get my variable decoded but the tablecell doesn't show up at all. if I remove init with coder i do get my tablecell interface showed, but can't get variable decoded. What should I do to make this work?
I think i can see your problem.
still, you shouldnt save the cells themselves. just create a UITableViewCell-Subclass that accepts one object from which it will gather all of its content. if you need more than one class for this, thats fine too. You can easily create several subclasses for several usecases.
then just save the data-objects and recreate the cells when you need them again.
the reason for this is that your way creates massive overhead, because you are trying to save all the UIviews, Lables, etc that make up your cell. Also, UITableVIews are highly optimized and try to reuse existing cells which does not play well with your approach.
TL;DR:
You need dataobjects, which you can save in keyedArchivers, and you need tableviewcells that accept these objects and display the necessary data. Seriously, you will save yourself some trouble. Let me know if you need help and have fun
Is there a method that is always called in Cocoa? Many classes have init or initWith, but even worse they can be loaded from a nib or something. I don't want to have to scrape around and find how it does this in this case. I just want to set some initial variables and other things, and I want a method to subclass that I can depend on no matter if it's a UIView, UIViewController or UITableViewCell etc.
No there is not such a method. init comes from NSObject so every object can use it, and as well subclasses define their own initialization methods. UIView, for example, defines initWithFrame: and furthermore there are init methods from protocols, such as NSCoding which defines initWithCoder:. This is the dynamic nature of objective-C, anything can be extended at any time. That being said, there are some patterns. UIViewController almost always takes initWithNibName:bundle: and UIView almost always takes initWithFrame: or initWithCoder:. What I do is make an internal initialize method, and just have the other inits call it.
- (void)initialize
{
//Do stuff
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self initialize];
}
}
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if(self)
{
[self initialize];
}
}
Not 100% sure that it is always called, but I am pretty sure that this is a viable option. To be perfectly honest, I can't recall that I have ever seen this method used in practice and I usually shy away from using this method (I have absolutely no idea why, probably because it's just not the cleanest and most comprehensive method to achieve this...):
-didMoveToSuperview()
From documentation:
Tells the view that its superview changed.
The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.
There's many ways you can write a custom initializer.
- (id)initWithString:(NSString *)string {
if((self == [super init])) {
self.string = string;
}
return self;
}
That's just how I write my initializers in general. For example, the one above takes a string. (you don't have to pass strings if you don't want).
Btw, init is a method. According to the header for NSObject, init has a method implementation.