UIViewController with custom view and weak reference - ios

I prefer to create custom views for all my view controllers. And I define it in code by using weak references for custom views like this:
#import "MyViewController.h"
#import "MyCustomView.h"
#interface MyViewController ()
#property (nonatomic, weak) MyCustomView *customView;
#end
#implementation MyViewController
- (void) loadView
{
MyCustomView *view = [MyCustomView new];
self.view = view;
self.customView = view;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// work with custom view
self.customView.tableView.delegate = self;
....
}
#end
Is this the correct use of weak references? Will the application crash or leak, or will there be other problems?

In this case weak is fine. You assign your CustomView to self.view which is defined in the UIViewController header as
#property(nonatomic,retain) UIView *view;
so the view property has a retaining reference.
There is a possibility that your view and customView could get out of sync - so I would be tempted to define customView as readonly and implement the getter as
- (CustomView *)customView
{
return (id)self.view;
}

As you can see in the documentation of UIViewController the view controller's view property has a strong reference to the view. So the custom view object will be retained as long as you don't set the view property to something else. In short, your method works.

As you create the instance from within this controller programatically, you should use a strong reference to set the ownership clearly to this controller.
In the event that you create the view object in IB or soryboard respectively, then a weak reference to the related IBOutlet would do.

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.

How do you use UIView with properties or variables inherited from ViewController?

I have UIViewController named ParentViewController.h and .m
Then I added UIView inside this ParentViewController.
I had uiview.h and uiview.h added and assigned to UIView inside ParentViewController.
From
-(void)drawRect:(CGRect)rect {}
which is located in uiview.m, I need to access to properties inside ParentViewController.
How do I do this? Am I using UIView wrong?
ParentViewController.h
#import <UIKit/UIKit.h>
#interface ParentViewController : UIViewController
//I want my uiview to access this variable.
#property (nonatomic, strong) NSMutableArray *usedByUIView;
#end
ParentViewController.m
#import "ParentViewController.h"
#import "uiview.h"
#implementation ParentViewController
- (void)viewDidLoad {
...
}
#end
uiview.h
#import <UIKit/UIKit.h>
#interface uiview : UIView
#end
uiview.m
#import "uiview.h"
#implementation uiview
-(id) initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self){
}
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"start drawing using the data from usedByUIView");
}
#end
There are a few answers on this subject but, summarizing them, you don't, at least not the way that you're doing it. UIView's do not have access to their view controller's and aren't supposed to need access. Of course, in the real world, sometimes it's not worth the overhead of coding around independent views so people hack in access to the controller access. This can be done by keeping an instance variable in the view, pointing to the controller, and assigning a reference to it after the view has loaded, or by overriding the init so you also pass a view controller, or lots of other ways. But before you do that think through the logic of why you want access to the controller from the view and see if there isn't a different way to do it.

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 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];

Setting the delegate of a UITextField/UITableView inside a Custom UIView to the managing ViewController

I have a CustomView UIView class that represents my screenUI in which I programatically create and position the UI elements, among which multiple UITextFields and a UITableView.
I also have a UIViewController and in it's loadView which I assign my CustomView to self.view.
I do this in order to best keep the cohesion of the view and the controller.
My question is how can I set the delegate of the UITextFields and UITableView inside the CustomView to the managing UIViewController?
It is my understanding that the UITextFieldDelegate and all other protocol methods should be implemented in the Controller and not the View, but I would like to avoid setting the delegate of the items in the view controller (e.g. textField1.delegate = self in the UIViewController class instead of textField1.delegate = <value for setting UIViewController as delegate> in my CustomView class) in order to keep my code cleaner.
Thank yous.
You could have a delegate property on your custom view:
customView.delegate = self;
self.view = customView;
In the delegate setter you could forward responsibilities:
-(void) setDelegate:(id<*the protocols you need*>) del
{
_textField.delegate = del;
...
}
You could have more than 1 type of delegate on your custom view, it depends on you needs, but one implementing all the protocols should usually suffice.
I would probably just forward them along. Create properties representing the delegates on your custom view. then in loadView set them to self.
// CustomView.h
#property (nonatomic, weak) id <UITextFieldDelegate> textFieldDelegate;
#property (nonatomic, weak) id <UITableViewDatasource> tableViewDataSource;
#property (nonatomic, weak) id <UITableViewDelegate> tableViewDelegate;
// Create textField
textField.delegate = self.textFieldDelegate;
// in ViewController
- (void)loadView
{
CustomView *view = //...
view.textFieldDelegate = self;
}

Resources