Access subclass property from super class - ios

I have a class that extends UIViewController called HorizontalModal, which, among other things, I want to automatically generate a back button on each view that extends it. If I put the code to draw the back button in the viewDidLoad method for HorizontalModal, nothing shows up. But when I put that same code in the viewDidLoad method for the class that inherits from HorizontalModal, the button shows up fine.
E.g:
self.backButton = [[BackButton alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
[self.view addSubview:self.backButton];
Seems to me that I'm missing something about class extension and inheritance in objective C. Clearly self.view in HorizontalModal and self.view in the child class refer to two different things. So is there a way to keep that method in the HorizontalModal class, but be able to add the back button to the child class view? IE, can I access a sub-class property from a super class? Or is there a different way of doing this (maybe with categories)?
Thanks

If I put the code to draw the back button in the viewDidLoad method
for HorizontalModal, nothing shows up. But when I put that same code
in the viewDidLoad method for the class that inherits from
HorizontalModal, the button shows up fine.
It sounds like your subclass's -viewDidLoad isn't calling it's superclass's -viewDidLoad, like:
- (void)viewDidLoad
{
[super viewDidLoad];
// subclass-specific view setup stuff here
}
That allows the superclass's version of -viewDidLoad to do its thing first, followed by whatever the subclass needs to do.
can I access a sub-class property from a super class? Or is there a
different way of doing this (maybe with categories)?
If the superclass needs some method that every subclass is expected to provide, then you should put that method in the superclass and let subclasses override it. Normally, the subclass's implementation of the method calls the superclass's version, but if that's required then you should document that fact.
The same is true for properties: if your superclass needs to access some property of the subclass (e.g. backButton), the superclass should define that property itself. The subclass is then free to change the value of the property or even provide different property accessors, and code in the superclass that uses the property will just work. In general, the superclass shouldn't have to know anything about its subclasses.
So, to be more concrete, let's say you have MyModal, a subclass of HorizontalModal:
#interface HorizontalModal : UIViewController
#property(strong) UIView *backButton;
#end
#implementation HorizontalModal
- (void)viewDidLoad
{
self.backButton = [[BackButton alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
[self.view addSubview:self.backButton];
}
#end
#interface MyModal : HorizontalModal
//...
#end
#implementation MyModal
- (void)viewDidLoad
{
[super viewDidLoad];
// more code specific to MyModal's view hierarchy
}
#end
Now the -viewDidLoad method of HorizontalModal will be able to access the backButton property, even if MyModal has changed the value of the property or overridden the accessors. Remember that self points to the exact same object in both versions of -viewDidLoad, so both versions have access to the same information.

Related

Initializing Views(button, label, etc) in -init or -loadview

Im curious, where is the best option to allocate/init, set attributes of views (uibutton, uilabel, uitextfield, initializing variables, etc).
This is in regards to developing an app strictly programatically. I see some cases where these views have been allocated/init in the class -init method, but then other times i see other views set in the -loadview method.
Can anyone provide some clarity about this? And maybe some abstract examples of when the best time to do it for either method would be.
Thanks
The -init* family of functions would be a good place to initialize simple properties, e.g. strings, numbers, and the like. The initializer runs just after the memory for the object is allocated, and if you have something that can be initialized there then you should do it there.
For UIViewController instances, you probably have to wait until the nib has been loaded before you can initialize everything else. If you've got images that need to be placed inside subviews, or fonts that need configuring, or whatever, then you need to have the nib loaded first. -viewDidLoad is the best place for that stuff.
For UIView instances (or subclasses like UITableViewCell), you need to wait for the nib to be loaded too. You can use -awakeFromNib in that case.
Here's a quick comment on this:
-SubClass a UIView, smash all your UI elements into that view, well as many as you can at least. Import this subclassed view's header into your view controller's implementation file
-In your view controller, typecast your view controller's view like so:
-(HHYSignUpViewFirstPhase*)contentView
{
return (id)[self view];
}
-Invoke the loadView method
-(void)loadView
{
[self setView:[HHYSignUpViewFirstPhase new]];
}
-In your viewdidLoad, you can now set handlers to buttons and such from your subclassed UIView by calling to "[self contentView]" like so:
-(void)viewDidLoad
{
[super viewDidLoad];
[self setTitles:#"Sign Up"];
[[[self contentView] nameField] setDelegate:self];
[[[self contentView] emailField] setDelegate:self];
[[[self contentView] passwordField] setDelegate:self];
[[[self contentView] signupButton] addTarget:self action:#selector(signupPressed) forControlEvents:UIControlEventTouchUpInside];
}
Now you have it all set up, you just need to add methods to handle events from the button, for example in the view did load from your subview that you subclassed:
-(void)signupPressed
{
///do work
}
UIVIew subclass:
HHYSignUpViewFirstPhase.h
#interface HHYSignUpViewFirstPhase : UIView
#property (nonatomic) UIButton * signupButton;
#property (nonatomic) UITextField * emailField;
#property (nonatomic) UITextField * nameField;
#property (nonatomic) UITextField * passwordField;
#end
HHYSignUpViewFirstPhase.m
#import "HHYSignUpViewFirstPhase.h"
#implementation HHYSignUpViewFirstPhase
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
//do work, set up buttons, constraints, etc, etc.
return self;
}
#end
Essentially, what I'm saying here is that in the subclassed UIView you can initialize the UIView and set up all its constraints and EVERYTHING, frames included and then in the load view method of your UIViewController, you then call to this view and typcast the view of the UIViewController. So, sometimes you do the set up in the init, sometimes you do it in the load view, it depends on what you are trying to do, but this is how you set this up in a pure programmatic fashion with separation of duties, encapsulation, and all tied together in an MVC framework -- all work is separated into classes, and all controllers control a single class.
http://matthewmorey.com/creating-uiviews-programmatically-with-auto-layout/
and this
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW36

How do I make a super class's custom init method only internally visible to its subclasses?

I currently have this init method for my superclass:
- (id)initWithFoo:(int)foo;
In a subclass, I have a custom init method that calls its superclass's init, like this. Bar is exclusive to the subclass.
-(id)initWithFoo:(int)foo Bar:(int)bar {
if (self = [super initWithFoo:foo]){
_bar = bar;
}
return self;
}
I run into problems when I create an instance of the subclass, because the compiler happily suggests the superclass init method in the list of possible initialization methods for my subclass instance, which I definitely do not want.
However, if I remove initWithFoo:(int)foo from the superclass's .h file then the subclasses can no longer use it within their own init methods.
Is there any way around this?
Yes, you can implement initWithFoo in your superclass and in your child make an "extension" declaration:
#interface SuperClass()
- (instancetype)initWithFoo:(int)foo;
#end
Make sure to place that declaration above #implementation in the .m file of your child

Unable to access self.parentViewController.property

I have implemented a subclass of ECSlidingViewController.
InitialViewController.h
#interface InitialViewController : ECSlidingViewController
#property (nonatomic, strong) WebViewController *webVC;
#property (nonatomic, strong) MenuViewController *menuVC;
#end
I need access to the .webVC from the .menuVC, but am unable to access them from self.parentViewController.webVC getting the error:
"UIViewController does not have a member named webVC"
but when I check in the debugger
po self: <MenuViewController>
po self.parentViewController: <InitialViewController>
po self.parentViewController.webVC: error: property 'webVC' not found on object of type 'UIViewController *'
However, when I navigate the object tree of self in the local variable window, I can go
-self <MenuViewController> 0x0000001
-UITableViewController
-UIViewController
-_parentViewController <InitialViewController>
-webVC <WebViewController
-menuVC <MenuViewController> 0x0000001
Where self and self.parentViewController.menuVC have the same memory address
When I navigate down and right-click->print Description, I get
Printing description of self->_parentViewController->menuVC:
<MenuViewController: 0x8e8c720>
Those '->' and the '_' make me think I'm seriously missing something involving pointers and ivars.
I'm also interested if there is a better way to implement this functionality. This kind of smells like I'm breaking MVC with a child Controller knowing about itself and its parent. However, selections on the menu directly manipulate the webView I have loaded.
Answer:
UIViewController does not have this property built in so you need to cast it:
[(InitialViewController*)self.parentViewController webVC]
Explanation:
This is a question of Classes and SubClasses. Say I have a class MyView which is a subclass of UIView. This class has a couple methods on it, -(void)method1; and -(void)method2;. The .h would look something like this:
#interface MyView : UIView
- (void)method1;
- (void)method2;
#end
I then create a MyView* named thisView and assign it to my ViewController's view property:
#import "MyView.h"
- (void)viewDidLoad
{
MyView* thisView = [[MyView alloc] init];
[self setView: thisView];
}
The UIViewController class's property, view is declared like this:
#property (...) UIView* view;
Pay extra special attention to the UIView* there.
Because view is declared as a UIView*, it will accept values that are subclasses of UIView*, but when I access it later, it will give me a MyView in a box labeled UIView.
In other words, consider this code:
- (void)viewDidLoad
{
MyView* thisView = [[MyView alloc] init];
[self setView: thisView];
UIView* returnedView = thisView; //This is perfectly fine
}
UIViewController will return something along the lines of returnedView, which is a MyView in a UIView container.
When your program goes to access the view, all it knows is that has received something in a box labeled "UIView", so it only knows that it can run methods on it that are present in the UIView class. We have to explicitly tell it "No, this is a MyView," and we do that with casting:
[self.view method1]; //This will break
[(MyView*)self.view method1]; //This will work
Back to your problem
When you access self.parentViewController, you are given a InitialViewController in a UIViewController box, so you have to explicitly tell your program that it is an InitialViewController. You do that by casting:
InitialViewController* parentVC = (InitialViewController*)self.parentViewController;
[parentVC.webVC doSomething];
Warning:
Your VC that is accessing webVC must have a declaration of (InitialViewController*), so you'll need to watch out for forward declaration errors.
If you are unfamiliar with this, it is when Class A imports Class B, but Class B also imports Class A:
A
--->Import B
B
--->Import A
Which results in
A
--->Import B
------->Import A
----------->Import B
and so on.
A class cannot import itself.

How can I share properties across custom and Apple-provided classes through inheritance?

I have a few different viewControllers that need to inherit the same properties, but aren't the same type of viewController. For example, one VC is a regular UIViewController, whereas another one is a UISplitViewController. Is there any way for me to efficiently use inheritance to make sure they all have these certain properties? Or do I just need to give each one their own separate declarations?
You can achieve what you want using a category on UIViewController. You can implement the properties in the category using associated objects.
See Objective-C: Property / instance variable in category for more details.
You could add a category to UIViewController. Since UISplitViewController inherits from UIViewController, it will have all properties and methods as defined in the category as well. However, categories have two limitations:
You can't add backing instance variables. You can create properties, but they can't have instance variables backing them. That means that if you are overriding the getter (and setter, if readwrite), so that it reads (or writes) an already existing property in some way, you're good. If not, you can look at associated objects.
Overriding methods in a category is a no-no. While nothing stops you from doing it, you have undefined behavior if another category overrides that method too. You just don't know which method will get executed. If you need to override methods, subclassing UIViewController would be better. However, UISplitViewController will then not know about these properties, unless you subclass it as well and add those same properties (in which case you're maintaining these properties twice).
I'm not sure what exactly do you need. If you don't want to (or can't) use common superclass with public properties, you can always write protocol. Only difference is that, protocol don't give you common implementation, but force you to write one (so you can be sure it is there, as you asked for).
Why not set up inheritance using a shared base class and set those shared properties in the init?
//MyBaseVC.h
#interface MyBaseVC : UIViewController
#property (nonatomic, strong) NSString *myString;
#end
//VC1.h
#interface VC1 : MyBaseVC
#end
//VC2.h
#interface VC2 : MyBaseVC
#end
-----
//(MyBaseVC.m)
-(id) init {
self = [super init];
if(self){
self.myString = #"Hello world!";
}
return self;
}
// VC1.m
-(id) init {
self = [super init];
NSLog(#"%#", self.myString); // "Hello world!"
return self;
}
// VC2.m
-(id) init {
self = [super init];
NSLog(#"%#", self.myString); // "Hello world!"
return self;
}
At that point, you can directly refer to the property on the subclassed objects:
NSLog(#"%#",myVc1.myString); //"Hello world!"
Otherwise, when you reference the VCs in a more generic fashion, you can always refer to their super class (MyBaseVC) - for example, if you need to pass them as a method parameter.
//-(void)doSomethingWithVC:(MyBaseVC *)vc;
[someObj doSomethingWithVc: vc1];

How to create a class with a UIViewController when the subclass of UIViewController is unknown

The title is what I think I need but i will go back one step. I want to create a class which handles certain things in an iOS app. This class might be called by multiple UIViewcontrollers in an iOS app. The class may need to show a UIView at some stage for user input. So my question is how can I show a UIView when I don't know which subclass of UIViewController is calling it? To what can I add the UIView from this class?
I suppose there are two possible answers either the class finds the current UIViewController or the calling subclass of UIViewController passes itself to the class so the class knows.
How is this supposed to be done.
Thanks guys for your help.
I'm going to expand on #ericleaf's comment regarding using a protocol and subclasses. It sounds like you are asking the following:
How can I create a resusable, generic class that presents a view
within a UIViewController subclass?
A great way to do this is to define a protocol in your generic class and have your view controller subclasses support this protocol. The protocol defines an interface for your custom class to comunicate with it's delegate, in this case a UIViewController subclass. Other than the protocol, the objects don't need to know anything else about the implementation of each other.
Any information your custom object needs to be able to present views within it's delegate would be passed via protocol methods. The specifics of the protocol are up to you based on your needs. You could have the custom object "ask" the delegate for information (e.g. what view should I put a subview in?) or you could have the protocol provide information to the delegate and let the delegate deal with it (e.g. here is a subview you can put wherever you want).
There is a lot of great documentation on protocols available on SO and elsewhere. This is long enough already so I kept the example fairly simple.
custom class .h file with protocol definition
// my custom class that adds adds a view to a view controller that supports it's protocol
// forward class definition for the protocol
#class MyAwesomeObject;
#protocol MyAweseomeObjectDelegate <NSObject>
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject;
#end
// this could be defined such that the delegate *must* be a UIViewController. I've left it generic.
#interface MyAwesomeClassObject : NSObject
#property (nonatomic, weak) id <MyAwesomeObjectDelegate> delegate;
#end
custom class .m file
// MyAwesomeObject.m
#import "MyAwesomeObject.h"
#implementation MyAwesomeObject
// this is a dumb example, but shows how to get the view from the delegate
// and add a subview to it
- (void)presentViewInDelegate
{
UIView *containingView = [self.delegate viewForMyAwesomeObject:self];
if (containingView) {
UIView *subview = [[UIView alloc] initWithFrame:containingView.bounds];
subview.backgroundColor = [UIColor redColor];
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[containingView addSubview:subview];
}
}
MyViewController .h using the custom object
// MyViewController.h
#import "MyAwesomeObject.h"
#interface MyViewController : UIViewController <MyAwesomeObjectDelegate>
#property (nonatomic, strong) MyAwesomeObject *awesomeObject;
#end
MyViewController .m using the custom object
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)init
{
self = [super init];
if (self) {
_awesomeObject = [[MyAwesomeObject alloc] init];
_awesomeObject.delegate = self;
}
return self;
}
// MyAwesomeObjectDelegate
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject
{
return self.view;
}
You can get the class into a string and do a compare.
For example, lets assume your custom UIViewController subclass is CustomViewCon and the UIViewController object reference is myUnknownClassObject, then:
NSString *classString = NSStringFromClass([myUnknownClassObject class]);
Then you can:
if([classString isEqualToString:#"CustomViewCon"]){
//do something like maybe present a particular view
myUnknownClassObject.view = myCustomView; //or anything..
}
Similarly you can check for any class.
Edit: According to the suggestions from comments, you could also do the following(better way):
if([[myUnknownClassObject class] isKindOfClass:[CustomViewCon class]]){
//same as before
}
Why wont you use a block for this?
BaseViewController.h:
#property (copy) void (^addViewBlock)();
- (IBAction)showViewWhenNeeded;
BaseViewController.m:
- (IBAction)showViewWhenNeeded
{
if (self.addViewBlock)
self.addViewBlock();
}
And in your child class, set that block's actions, and call the method when you feel like you should put up a view.
ChildViewController.m
// within some method, propably init or smth
[self setAddViewBlock:^{
[self.vied addSubView:...];
}];
// when need to actually add the view
[self showViewWhenNeeded];

Resources