Programmatically set an IB object's Class - ios

When using Interface Builder, a lot of the time I'd set the Class property to one of my custom classes. Say I have a UIView in the nib and I set the class to my MyView class.
That's all well and good, even when loading the nib programmatically.
But, is there a way to set the Class of a UI control when programmatically loading a nib? The reason being that I want to sometimes use the same nib created in IB, but have it associated with a different Class.

I don't think there is a proper way to achieve that programmatically.
You could create a naked UIView object in IB and give it a tag or assign it to an IBOutlet for its identification so taht you can access it programmatically in viewDidLoad.
In viewDidLoad you access it and fetch its frame and superview and probably background colour, alpha, hidden status etc, so that you can set all those values in IB. Then remove the UIView from its superview and nil the property (if any) or release the object respectively (depending on ARC or not). Then create the UI Element that you need, assign it to the property (if any) add it to the superview of the former UIView, set its frame and properties accordingly and go from there.
(There are ways in Obj-C to change an object's class after its instanciation, but I would not recommend doing that in this case, especially when your subclasses come with additional properties and ivars.)

Normally you create a UIView object on your UIViewController's class XIB file using Interface Builder and then you use the Custom Class tool in the Identity Inspector to associate the UIView with a class (The class is a UIView class containing the code for drawing on the UIView object.). Then you have to use #property and #systhesize in your UIViewController class to hook the UIView (using the connections inspector) to your class. This method is OK but in certain circumstances it has limitations.
You can get around all of this pragmatically.
Create the UIView class that will be used to draw on a UIView object. In this class you create the context reference (CGContextRef) to give the drawing tools a context (where to draw) for such things as strings, lines, circles, etc. i.e.
CGContextRef gg = UIGraphicsGetCurrentContext();
In the UIViewController class, in the .h file you need to import a reference to your UIView class. Let's call it: DrawOnView
#import "DrawOnView.h"
Then in the brackets encompassing the #interface place this line:
UIView * draw; // draw can be changed to any name that suits your needs
Then in the .m part of the class inside the viewDidLoad method you need to insert this code:
// Make the size and location that suits your needs
// You can change this on the go in your code as needed, such as if the
// device orientation is changed.
draw = [DrawOnView alloc] initWithFrame:CGRectCreate(50, 50, 100, 200)];
// You can change the background color of the view, if you like:
[draw setBackGroundColor:[UIColor greenColor]];
// Now add the view to your primary view
[self.view addSubview:draw];
Now, in other parts of our program, you can call methods you have declared in the DrawOnView class and refresh (which calls the drawRect method, the primary entry point in your UIView class) by using this reference:
[(DrawOnView*) draw setNeedsDisplay];
This is very important. Do not use:
[draw setNeedsDisplay]; // This will not work!
Let's say you have other methods defined in DrawOnView and want to call them.
Here's an example method (in the .h file):
-(BOOL) wasHotSpotHit: (CGPoint) p;
The actual method could look like this (in the .m file):
-(BOOL) washHotSpotHit: (CGPont) p
{
if(CGRectContainsPont(hotspot.frame, p))
{
return true;
}
return false;
}
Use code like this:
if([(DrawOnView*) draw testIfSpotHit:p])
{
// Do something for when user touches hot spot.
}

Try using "object_setClass(id object, Class cls)" method of Objective c runtime. GoodLuck:)

Related

Subview acessing superview methods

How can a subview access the methods from its superview? I have a button, and when pressed I would like the button to call a method from its superview, but I do not know how.
suppose your super View class name is
MainView.h
sub View Name
SubView.h
So In sub class you can do
MainView *myMainView = (Mainview *)[self superview];
[myMainView someMethod];
Make sure someMethod is public Method.
Other way you could have reference to all the view is set a tag
For example
myMainView.tag = 100; or self.tag = 100;
In the subview you could do
MainView *myMainView = (Mainview *)[self viewWithTag:100];
[myMainView someMethod];
a weird construct but just call the method:
inside a view you have have self.superview
since self.superview is a UIView*, the compiler will claim it is invalid to call method XYZ on it. Cast it to id or to your class name to use it
e.g.
[(id)self.superview myMethod];
or even
id myValue = [(id)self.superview myMethod:param1];
One method is to use delegates.
#protocol ButtonHandlingDelegate <NSObject>
- (void) subviewButtonWasPressed;
#end
In your Subview add this:
#property (nonatomic, weak) id selectionDelegate;
When subview is created, set delegate to superview.
Define Superview as delegate in .h file
#interface SuperView : UIView <ButtonHandlingDelegate>
in Superview .m file
- (void) subviewButtonWasPressed{
// Do somethign about it
}
All of the answers listed are hacky and bad style. You should be using delegation through the subview's ViewController. What you will want to do is create a protocol for your subview with a void method called something like specialButtonPressedOnView:(SUBVIEWCLASS *)view. Then in the subview's view controller you should make yourself the delegate for this protocol and respond to the delegate method from the VC's context.
Using self.superview is a bad idea because you cannot guarantee what your superview is going to be and generally blindly calling method on objects (ESPECIALLY those cast to id) is a really bad idea.
The other answer that suggested having the superview implement the subview's protocol is also not good style because you're creating a dependency between your views. If you were thinking of going down this path your subview should probably just be a subclass of the superview.
One of the core parts of the MVC design pattern is making sure that your views are reusable and totally independent. They are just structures that you can inject your content into, to which they will respond with pretty UI, and they don't need to know anything about what's being passed to them and don't have to ask other views for help. So either using delegation through the subview's ViewController or subclassing is probably the best direction. Both methods preserve the MVC pattern and are much safer than the other suggestions.

How to set backgroundColor in loadView?

I want to override the loadView method of my custom view controller in order to customise the view that is being shown. For that custom view I want to apply the background color that was set in the Storyboard editor.
If I for example implement loadView like this:
-(void)loadView{
self.view = [[MyCustomView alloc] init];
//Do some initialisation work for the custom view
//...
}
Then the resulting view has a black background. However, if I only call the default implementation:
-(void)loadView{
[super loadView];
}
Then the orange background color (see screenshot) is applied to the view. How can I get the color that was set in Storyboard editor? I would like to do something like this (pseudo code):
-(void)loadView{
self.view = [[MyCustomView alloc] init];
self.view.backgroundColor = [self colorThatWasSelectedInStoryboardEditor];
//Do some initialisation work for the custom view
//...
}
How does apple achieve that in the default implementation of loadView?
Since you apparently really want to use "loadView" here instead of "viewDidLoad", AND Apple's documentation for loadView says NOT to call "super", you will need to "hide" your custom color somewhere else. E.G. some other view (e.g. a hidden one?) in your ViewController, where it's connected to an IBOutlet and where you can extract it via the view's ".backgroundColor" property.
Or you will need to set your ".backgroundColor" property via good ol' UIColor methods like "colorWithRed:green:blue:alpha:".
All your views from the Storyboard are laid out before viewWillAppear:, which means, in viewDidLoad you don't have them yet. Try moving your code to viewWillAppear:, or even better, to viewWill/DidLayoutSubviews and than self.view.backgroundColor will give you the one you set in the IB.
To answer the question in your comment, go to the IB, select your view, go to the Identity Inspector tab under the Utilities (right) pane, and set a custom class to be MyCustomView. No need to implement loadView to do that.
e.g. a UIView that I have declared as a custom view class AGBlurView:

Should i create a view ( consisting UIButton, UILabel etc) in a separate UIView class or inside UIViewController?

I have a UIViewController say viewControllerA which contains some view element like UIButton, UILabel etc. Now my question is should I create those view elements in a separate UIView class and then add in UIViewController, or should I create those view elements directly inside the UIViewController. Accordingly to MVC isn't it appropriate to create view elements inside a separate UIView class and then add this in UIViewController?
The standard place to build the view hierarchy in a UIViewController is in the -viewDidLoad method. That method gets called whenever the UIViewController's view is created. The view controller's view will be loaded from the NIB/Storyboard if applicable; your outlets will be wired up; and then -viewDidLoad is called for you to perform further customization:
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0,0.0,100.0,40.0)];
[self.view addSubView:aLabel];
}
In Cocoa/Cocoa Touch you don't always want to subclass everything the way you would in, say, Java. There are often other preferred means of extending the functionality in built-in classes such as Objective-C categories, delegation, and pre-defined properties.
It's certainly possible to do this sort of thing another way, but this is the most "Cocoa-like" way to do it. Actually, the most "Cocoa-like" way would be to create the view hierarchy in Interface Builder, but if you want to do it programmatically this is the usual way.

Defining custom UIViews in storyboard

I want to show my own custom UIView in storyboard. By far I have done following but my custom view is not showing up.
Dragged and dropped a UIView instance in my screen.
Defined the class for this UIView as my custom class.
Have connected this UIView with an IBOutlet in my view controller.
I even tried with below code in viewWillAppear.
self.myView = [[MyCustomView alloc] initWithFrame:frame];
This works if I create an instance of my custom view and add as a subview to my IBOutlet property for my view. So, below code is working but I want to keep track of only my IBOutlet iVar and do not want to play with another object for changes on my custom view:
self.myExtraView = [[MyCustomView alloc] initWithFrame:frame];
[self.myView addSubview:self.myExtraView];
Any idea how to do this in a better way so that I could have just one reference of my custom view and could change properties on it as per will.
Found the issue. With storyboard we must initialize anything in initWithCode method. I was implementing the regular init method.

How to use a custom UIButton category with Interface Builder?

I'm coding a simple card game for my younger sister on a whim. I'm using UIButtons: default state is face down, selected state is face up. I need the buttons to have a boolean property that tells me if they've ever been flipped over. If not, it gets set to true (that way I'm not just drawing random cards on each flip). I tried creating a category of UIButton called CardGameButton. In the .h file:
#interface UIButton (CardGameButton)
#property (nonatomic) BOOL discovered;
#end
In the .m file:
#implementation UIButton (CardGameButton)
#dynamic discovered;
#end
This is really all I need. How do I use this in IB? I have a bunch of UIButtons on a screen that I want to be CardGameButtons. But when I try to switch my calls to UIButton in my view controller to CardGameButton, it tells me CardGameButton isn't a type (yes I imported the file). And when I try to switch the class of the UIButtons in the storyboard to CardGameButtons, the console tells me that they're an "unknown class". I tried subclassing UIButton, but that didn't work (I just need them to be RoundedRectButtons with the extra property, and since you can't subclass RoundedRectButton they wouldn't display properly). How do I make this work in IB?
You're treating CardGameButtons as though it's a subclass of UIButton when instead it's a category. Anything you declare as UIButton will have the discovered property as standard, but you can't 'create' a CardGameButtons as it's not a thing in itself.
Categories are not types. They're just used to add some extended behavior to the class.
I suggest that you try subclassing UIButton and call it CardGameButton. Add the BOOL property to its interface. You can still make it a round rect button, by setting buttonType to UIButtonTypeRoundedRect "round rect buttons aren't a separate subclass.". Then override
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
if(event.type == UIEventTypeTouches){
self.discovered = YES; // or handle the logic as you want here
}
[super sendAction:action to:target forEvent:event];
}
You can add a UIButton to your view from the IB, and then its class to your created subclass and set its type to round rect.
Edit: As you stated, buttonType is actually readOnly, so we can't use that to draw a round rect button. However, it turns out that it's not hard to draw it.
Check this tutorial. It shows how to do it using CALayer property of UIButton and using UIBezeirPath.

Resources