Custom UIView in Code - ios

Ive created a UIView in code using the addSubview:view method. If I want this to be a custom class rather than the standard UIView, does all this customisation have to take place in the view controllers viewDidLoad method? Seems like there will be alot of code in the viewDidLoad if this is the case! This is the first time ive attempted to create a view in code - the other times ive done it in IB where Ive created a custom class and changed the class of the view in the identity inspector.

Create a new UIView subclass
// MyView.h
#interface MyView : UIView
// public properties, method declarations here
#end
// MyView.m
#implementation MyView
// implementation here, including anything you want to customize this view's
// look or behavior
#end
Then instantiate it in your view controller by importing and referring to the custom class
// ViewController.m
#import "MyView.h"
- (void)viewDidLoad {
[super viewDidLoad];
MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:myView];
}

Related

init method for custom SCNView

I have created a custom class "CustomSCNView" that inherits from SCNView. I want to use the custom class in another view controller. So I need to create a CustomSCNView object and use it to another class to manipulate things. But how can I create a CustomSCNView object in another class.
This is not working:
CustomSCNView *customView = [[CustomSCNView alloc]init]; //in viewcontroller.m
Sorry forgot to mention I used the interface builder to drag a SCNView to the view controller and then set its class to CustomSCNView.
I'm a bit confused by your question, but I've created a sample project at https://github.com/NSGod/CustomSCNView that may do what you're looking for.
First, the storyboard has 2 CustomSCNViews laid out side by side in the ViewController's view. Like you did, I dragged 2 SCNViews from the IB palette to the view and then set the custom class to be CustomSCNView.
Second, is the CustomSCNView class which is defined as follows:
#import <Cocoa/Cocoa.h>
#import <SceneKit/SceneKit.h>
#interface CustomSCNView : SCNView
#property (nonatomic, assign) BOOL allowsRotation;
#end
You can see, it has an allowsRotation property that any other object can set.
To set a default value for allowsRotation, other than NO, you can override initWithCoder: which is what's used when you set up the views in Interface Builder like you did:
#import "CustomSCNView.h"
#implementation CustomSCNView
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super initWithCoder:coder])) {
_allowsRotation = YES;
}
return self;
}
#end
The ViewController then has 2 IBOutlets to both CustomSCNViews.
#import <Cocoa/Cocoa.h>
#import <SceneKit/SceneKit.h>
#class CustomSCNView;
#interface ViewController : NSViewController
#property (weak) IBOutlet CustomSCNView *sView1;
#property (weak) IBOutlet CustomSCNView *sView2;
#end
ViewController.m:
#import "ViewController.h"
#import "CustomSCNView.h"
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_sView1.allowsRotation = NO;
_sView2.allowsRotation = YES;
}
#end
You can see that in viewDidLoad, you can set the allowsRotation property of both views to whatever you want. When you run this application, 2 instances of CustomSCNView are created for you automatically (via initWithCoder:), when the storyboard/nib files are loaded. There's no need to create another instance of a CustomSCNView to be able to set the properties of the 2 existing instances you already have.
If you look at the documentation for SCNView it tells you:
You can create a SceneKit view by using its initWithFrame:options:
method or by adding it to a nib file or storyboard.
So you cannot use the init method unless you have implemented your [CustomSCNView init] method to call [super initWithFrame:options:].
If you need access to custom subclass properties from Interface Builder, mark those properties IBInspectable (and possibly implement IBDesignable). That's documented by Apple here, and nicely summarized on NSHipster.
In any initialization path, you must call the superclass's designated initializer. For SCNView, that appears to be initWithFrame:options: (not documented as such, but the header strongly implies it). See this document on multiple initializers and subclassing.
That said, though, subclassing SCNView is a code smell that you might be fighting the framework and working too hard.

Accessing interface builder object from view controller

I'm completely new to Objective-C, XCode, and iOS development and I'm trying to figure out how to run certain code at startup, after all UI views and controls have been instantiated. I have a generic NSObject that I've added through interface builder by dragging it into my view controller scene. It's defined as follows:
#import <Foundation/Foundation.h>
#interface Controller : NSObject {
IBOutlet UISlider *slider;
IBOutlet UILabel *label;
}
-(IBAction)sliderChanged:(id)sender;
#end
I need to run sliderChanged on initialization. I've tried the following way:
#import "Controller.h"
#implementation Controller
-(id)init {
self = [super init];
if (self){
[self sliderChanged:nil];
}
return self;
}
// More code here
But both my slider and label are nil when this is called. I understand there's a viewDidLoad method within the ViewController class which may be what I need, but I'm not sure how to access the instance of my Controller class (which seems to be instantiated somewhere behind the scenes by the interface builder) from within that method. Should all of this code simply be moved to the ViewController itself? That would seem to make sense, but the design above is what we've been instructed in class, so I'm not really sure how to go about doing this.
After the XIB/Storyboard loader finishes loading all the objects and wiring them up, it sends awakeFromNib to every object that was instantiated from the XIB. So try adding this to your Controller class:
- (void)awakeFromNib {
[super awakeFromNib];
[self sliderChanged:nil];
}
You can find more information in the NSObject UIKit Additions Reference and “The Nib Object Life Cycle” in the Resource Programming Guide.
HOWEVER, if you created Controller as a top-level object, and you didn't connect any outlets to it, then nothing references it after the XIB loader finishes with it, so the system will deallocate it again. That's probably not what you want, so you should connect an outlet in your view controller to the Controller. If you do that (and let's say the outlet is named controller), then you can access it in viewDidLoad in your view controller class:
#import <UIKit/UIKit.h>
#import "Controller.h"
#interface ViewController : UIViewController {
IBOutlet Controller *controller;
}
#end
Implementation:
- (void)viewDidLoad {
[super viewDidLoad];
[self.controller sliderChanged:self];
}

How to pass values from a subclass of UIView to UIViewController class

I have a class Graph that subclasses UIView.
I am in my view controller class and I call the SliderView class with the following code
- (void)viewDidLoad
{
[super viewDidLoad];
if (self) {
// Custom initialization
SliderView = [[SliderView alloc] init];
}
[self setView: SliderView];
}
I'd like to be able to access to the properties I have setup in SliderView in my view controller class. How can I do this?
Use the getters and setters like Kyle said (he just went further showing the property).
[graph propertyName] and [graph setPropertyName:newValue]
I would also take a look at: http://cocoadevcentral.com/d/learn_objectivec/
You should just be able to access the properties of the Graph class. In your Graph class you might have some thing along the lines of:
#property (nonatomic, strong) NSObject *propertyName;
You would access it in the UIViewController by way of something like
[(Graph *)self.view propertyName];

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
}

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