UIView and UIViewController practice for inheritance - iphone

I was just wondering if this approach looks like a good practice for apps with a lot of custom views, for nested PNG graphics and animations that may change based on user interaction. I created a BaseView class that extends UIView
#interface BaseView : UIView {
#protected
BaseViewController *controller;
}
#property (retain) BaseViewController *controller;
#end
and a corresponding controller class which is the primary location which I am putting code to manipulate the view
#interface BaseViewController : UIViewController {
#protected
CGRect drawArea;
}
- (void) drawArea:(CGRect) _drawArea;
- (CGRect) drawArea;
- (void) linkSubviewController: (BaseViewController *) _subviewController;
#end
where "drawArea" is the CGRect used to pass to the view as a frame.
"linkSubviewController" allows you to nest a controller and view as follows :
- (void) linkSubviewController: (BaseViewController *) _subviewController {
[self.view addSubview:[_subviewController view]];
}
In addition I layered another custom pair called "ImageView" and "ImageViewController" which extend BaseView but also store a UIImage and an x,y,w,h
In the "drawRect" drawing methods on views I can check to see if any vars in the self.controller vars have been changed, or assign images, for example :
UIImage *image = [(ImageViewController *)self.controller image];
CGContextDrawImage( ... ) etc
I write most of the loadView methods something like this
- (void)loadView {
ImageView *v = [[ImageView new] initWithFrame:drawArea];
v.controller = self;
self.view = v;
}
The base "initWithFrame" routine contains
self.backgroundColor = [UIColor clearColor];
self.opaque = NO;
So I can load a variety of images with transparent backgrounds without having to assign that each time.
I've been able to use this code throughout my app and it seems to make it easy to write a top level class which assembles the layout of custom items. For animations I've been putting them in the controllers and manipulating self.view.layer.
Basically I am looking for feedback, I am new with Objective-C and the IPhone SDK

There are several issues here:
Using [[Classname new] init...] is incorrect usage of new. Using new is short for [[Classname alloc] init] so you are effectively calling init twice.
Views shouldn't really need to know who is controlling them.
Your view is retaining the controller, and since UIViewController retains its view, you have a retain cycle and neither will ever be fully released.
If you want this type of behavior (whereby a view can delegate its drawing to a parent), try creating a DrawDelegate protocol, have your controllers implement that protocol, and in your view subclass have a non-retaining drawDelegate property:
#protocol DrawDelegate
- (void) drawArea:(CGRect)rect;
#end
#interface BaseView : UIView {
id<DrawDelegate> drawDelegate;
}
#property (assign) id<DrawDelegate> drawDelegate;
#end

Related

How to add a UIImage with UIGesture to most views in an application.

I want to make an Advertising banner in my app. A bit like iAd's.
I was going to make it by having a UIImage on the view then assigning the banner image. I would then add a touch gesture so the user could click it and go to another view in my app. I Know That I can do this on one view quite easily but I want this to be on most views in the app. Whats the best way for adding the banner to more than one view with out writing the same code more that once?
The below design shows the sort of banner im after.
Thanks
#import <UIKit/UIKit.h>
#class custom;
#protocol adDelegate
- (void)viewAd:(NSString *)adRate;
#end
#interface custom : UIView
#property (strong, nonatomic) UIImage *viewImage;
#property (assign) id <adDelegate> delegate;
#end
// Main class
#import "custom.h"
#implementation custom
#synthesize viewImage;
#synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = viewImage;
[self addSubview:imageView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate viewAd:#"view"];
}
You can Create a UIView Class and call it BannerView for instance.
// in the bannerView.h
#import <UIKit/UIKit.h>
#interface BannerView : UIView{
UIImageView* bannerImage;
}
#property(nonatomic,retain) UIImageView* bannerImage;
#end
//in the bannerView.m
#import "BannerView.h"
#implementation BannerView
#synthesize bannerImage;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
bannerImage=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"banner-image.png"]];
bannerImage.frame=CGRectMake(0, 0, 320, 100);
[self addSubview:bannerImage];
// add a uibutton on top of the uiimageview and assign an action for it
// better than creating an action recogniser
UIButton* actionButton=[UIButton buttonWithType:UIButtonTypeCustom];
actionButton.frame=CGRectMake(0, 0, 320, 100);
[actionButton addTarget:self action:#selector(yourAction) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:actionButton];
}
-(void) yourAction{
// do what ever here like going to an other uiviewController as you mentionned
}
#end
Now you can call this view from any View Controller this way
BannerView* banner=[[BannerView alloc] initWithFrame:CGRectMake(0, 300, 320, 100)];
[self.view addSubview:banner];
Try creating a parent class from UIView where you do all the display and handling of the banner using your UIImageView and gesture recognizers. Then whichever views need this functionality, derive them from this parent class, and override default handling in method so that you can customize the behavior in your child class.
A few suggestions:
First, why not just use a UIButton instead of a UIImage with a Gesture? All you're really doing is replicating button functionality after all...
Second, I'd probably tackle the overall problem by creating a class that includes the UIButton, like so:
#interface YourSuperViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIButton *adButton;
- (IBAction)adTouched:(id)sender;
#end
In the viewDidLoad for this class, create the button, and add it to the view, and add your ad-specific logic to the adTouched action.
Then create the rest of the views in your app as an instance of YourSuperViewController. Like so:
#interface SomeOtherViewController : YourSuperViewController
Now the SomeOtherViewController will auto-magically have the ad button and respond to a touch on it properly. Done!
What everyone else has said is the best way. If you need custom functionality, subclassing is probably the way to go.
I just wanted to add one pedantic thing. Its important to remember that a UIImage is not a view. There has never been a UIImage on the screen, ever. A UIImage is a model object. It is just a collection of data. A UIImageView is a view object and as such, a UIImageView can display itself on the screen.
This might seem overly pedantic and nitpicky, but its important to have these things sorted out in our heads in order to effectively use MVC (model, view, controller)

iOS iPhone is it possible to clone UIView and have it draw itself to two UIViews?

I'm thinking of a way to have a UIView render itself onto another UIView as well as the first one. So I have my main UIView with it's bounds, and the UIView also renders itself in some other UIView.
Is this possible ? Does it require extensive layer operations?
Don't know whats your real intention is, but this will draw the view twice, userinteraction etc. will not work on the second view. Also this solution does not take care of different frame sizes.
Header of the View you want to clone
#interface SrcView : UIView
#property(nonatomic, readonly, strong) UIView *cloneView;
#end
#interface CloneView : UIView
#property(nonatomic, weak) UIView *srcView;
- (id)initWithView:(UIView *)src;
#end
implementation of the View you want to clone
#import "SrcView.h"
#import "CloneView.h"
#implementation SrcView
#synthesize cloneView;
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
[cloneView setNeedsDisplay];
}
- (UIView *)cloneView {
if (!cloneView) {
cloneView = [[CloneView alloc] initWithView:self];
}
return cloneView;
}
#end
#implementation CloneView
#synthesize srcView;
- (id)initWithView:(UIView *)src {
self = [super initWithFrame:src.frame];
if (self) {
srcView = src;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
[srcView.layer renderInContext:UIGraphicsGetCurrentContext()];
}
#end
now you can just call cloneView and add it somewhere you want.
This seems to be an oft asked question here on StackOverflow. For example:
iphone, ipad Duplicate UIView - Cloned View
Copying the drawn contents of one UIView to another
UIView duplicate
Duplicate, clone or copy UIView
But if it were me doing this, my first approach would be to get a handle to the UIView I want to copy, then recursively iterate all the subviews of it and then copy & add them as subviews to the UIView I want to copy the main UIView into.
I can't imagine there's too much layer operations going on with this, but you would likely need to figure out how to programmatically re-establish outlets and/or actions.
There is no easy way to clone a view and then to update two views by one line of code. Because their underlying CALayers are different. But for duplicating a UIView, here is a new method you can use: Use UIView's method:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
This is the fastest way to draw a view. Available in iOS 7.
Create another instance of the UIView you wish to "clone" and add it as a subview to another view. You don't really clone a UIView object, you simply create another instance of it.

In Objective-C with no nib file is MVC to have the logic together with the view in same class?

When I work in Objective-C programatically with out nib files, and have the logic in my:
appViewController.m
having in the same class What is going on with that view, as well as with the View elements? Is this against the MVC pattern?
Do I have to create another class and message both classes?
It's up to you! If you want to separate layers (M,V,C) you can create your own view programmatically and, by using composite design pattern, build it in your UIView subclass, by removing drawing code from your controller.
That is...
You create a "CustomCompositeView" that extends UIView
in layoutSubview (hinerited from UIView) you will draw all your UI elements
in your CustomViewController you will display your view using loadView:
code:
- (void)loadView
{
CustomCompositeView *mainView = [[CustomCompositeView alloc] initWithFrame:aFrame];
[self setView:mainView];
[mainView release]; // remove this line if you are using ARC!
}
Technically, it is going against the MVC pattern. Your V and C and combined into a single object. You can seperate the code that handles layout and drawing into a seperate UIView subclass. Then load it with loadView:
// MyViewController.m
- (void)loadView {
MyView* myView = [[[MyView alloc] init] autorelease];
myView.delegate = self;
self.view = myView;
}
#pragma mark - MyViewDelegate Methods
- (void)myViewSaveButtonWasPressed:(MyView *)myView {
// do something
}
To communicate between the view and the view controller, you can define a delegate protocol.
// MyView.h
#class MyView;
#protocol MyViewDelegate <NSObject>
- (void)myViewSaveButtonWasPressed:(MyView *)myView;
#end
#class MyView : NSObject
#property (nonatomic, assign) id<MyViewDelegate> delegate;
// ...
When a button is pressed in the view (or something else along those lines) pass that on to the delegate. The ViewController should conform to the delegate method and handle the actual logic itself that way.

Using CALayer Delegate

I have a UIView whose layers will have sublayers. I'd like to assign delegates for each of those sublayers, so the delegate method can tell the layer what to draw. My question is:
What should I provide as CALayer's delegate? The documentation says not to use the UIView the layers reside in, as this is reserved for the main CALayer of the view. But, creating another class just to be the delegate of the CALayers I create defeats the purpose of not subclassing CALayer. What are people typically using as the delegate for CALayer? Or should I just subclass?
Also, why is it that the class implementing the delegate methods doesn't have to conform to some sort of CALayer protocol? That's a wider overarching question I don't quite understand. I thought all classes requiring implementation of delegate methods required a protocol specification for implementers to conform to.
Preferring to keep the layer delegate methods in my UIView subclass, I use a basic re-delegating delegate class. This class can be reused without customization, avoiding the need to subclass CALayer or create a separate delegate class just for layer drawing.
#interface LayerDelegate : NSObject
- (id)initWithView:(UIView *)view;
#end
with this implementation:
#interface LayerDelegate ()
#property (nonatomic, weak) UIView *view;
#end
#implementation LayerDelegate
- (id)initWithView:(UIView *)view {
self = [super init];
if (self != nil) {
_view = view;
}
return self;
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
NSString *methodName = [NSString stringWithFormat:#"draw%#Layer:inContext:", layer.name];
SEL selector = NSSelectorFromString(methodName);
if ([self.view respondsToSelector:selector] == NO) {
selector = #selector(drawLayer:inContext:);
}
void (*drawLayer)(UIView *, SEL, CALayer *, CGContextRef) = (__typeof__(drawLayer))objc_msgSend;
drawLayer(self.view, selector, layer, context);
}
#end
The layer name is used to allow for per-layer custom draw methods. For example, if you have assigned a name to your layer, say layer.name = #"Background";, then you can implement a method like this:
- (void)drawBackgroundLayer:(CALayer *)layer inContext:(CGContextRef)context;
Note, your view will need a strong reference the instance of this class, and it can be used as the delegate for any number of layers.
layerDelegate = [[LayerDelegate alloc] initWithView:self];
layer1.delegate = layerDelegate;
layer2.delegate = layerDelegate;
The lightest-wight solution would be to create a small helper class in the the file as the UIView that's using the CALayer:
In MyView.h
#interface MyLayerDelegate : NSObject
. . .
#end
In MyView.m
#implementation MyLayerDelegate
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
. . .
}
#end
Just place those at the top of your file, immediately below the #import directives. That way it feels more like using a "private class" to handle the drawing (although it isn't -- the delegate class can be instantiated by any code that imports the header).
Take a look at the docs on formal vs informal protocols. The CALayer is implementing an informal protocol which means that you can set any object to be its delegate and it will determine if it can send messages to that delegate by checking the delegate for a particular selector (i.e. -respondsToSelector).
I typically use my view controller as the delegate for the layer in question.
A note regarding "helper" classes for use as a layer's delegate (with ARC at least):
Make sure you store a "strong" reference to your alloc/init'd helper class (such as in a property). Simply assigning the alloc/init'd helper class to the delegate seems to cause crashes for me, presumably because mylayer.delegate is a weak reference to your helper class (as most delegates are), so the helper class gets freed up before the layer can use it.
If I assign the helper class to a property, then assign it to the delegate, my weird crashes go away, and things behave as expected.
I personally voted for Dave Lee's solution above as being the most encapsulating, particularly where you have multiple layers. However; when I tried it on IOS 6 with ARC I got errors on this line and suggesting that I need a bridged cast
// [_view performSelector: selector withObject: layer withObject: (id)context];
I therefore amended Dave Lee's drawLayer method from his re-delegating delegate class to employ NSInvocation as below. All usage and ancillary functions are identical to those Dave Lee posted on his earlier excellent suggestion.
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context
{
NSString* methodName = [NSString stringWithFormat: #"draw%#Layer:inContext:", layer.name];
SEL selector = NSSelectorFromString(methodName);
if ( ![ _view respondsToSelector: selector])
{
selector = #selector(drawLayer:inContext:);
}
NSMethodSignature * signature = [[_view class] instanceMethodSignatureForSelector:selector];
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:_view]; // Actually index 0
[invocation setSelector:selector]; // Actually index 1
[invocation setArgument:&layer atIndex:2];
[invocation setArgument:&context atIndex:3];
[invocation invoke];
}
I prefer the following solution. I would like to use the drawLayer:inContext: method of the UIView to render a subview that I might add without adding extra classes all over the place. My solution is as follows:
Add the following files to your project:
UIView+UIView_LayerAdditions.h with contents:
#interface UIView (UIView_LayerAdditions)
- (CALayer *)createSublayer;
#end
UIView+UIView_LayerAdditions.m with contents
#import "UIView+UIView_LayerAdditions.h"
static int LayerDelegateDirectorKey;
#interface LayerDelegateDirector: NSObject{ #public UIView *view; } #end
#implementation LayerDelegateDirector
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
[view drawLayer:layer inContext:ctx];
}
#end
#implementation UIView (UIView_LayerAdditions)
- (LayerDelegateDirector *)director
{
LayerDelegateDirector *director = objc_getAssociatedObject(self, &LayerDelegateDirectorKey);
if (director == nil) {
director = [LayerDelegateDirector new];
director->view = self;
objc_setAssociatedObject(self, &LayerDelegateDirectorKey, director, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return director;
}
- (CALayer *)createSublayer
{
CALayer *layer = [CALayer new];
layer.contentsScale = [UIScreen mainScreen].scale;
layer.delegate = [self director];
[self.layer addSublayer:layer];
[layer setNeedsDisplay];
return layer;
}
#end
Now add the header to your .pch file. If you add a layer using the createSublayer method, it will automagically show up without bad allocs in the override to drawLayer:inContext:. As far as I know the overhead of this solution is minimal.
It's possible to implement a delegation without resorting to a strong ref.
NOTE: The basic concept is that you forward the delegate call to a selector call
Create a selector instance in the NSView you want to get the delegation from
implement the drawLayer(layer,ctx) in the NSView you want to get the delegation from call the selector variable with the layer and ctx vars
set the view.selector to a handleSelector method where you then retrieve the layer and ctx (this can be anywhere in your code, weak or strongly referenced)
To see an example of how you implement the selector construction:(Permalink) https://github.com/eonist/Element/wiki/Progress#selectors-in-swift
NOTE: why are we doing this? because creating a variable outside methods whenever you want to use the Graphic class is non-sensical
NOTE: And you also get the benefit that the receiver of the delegation doesn't need to extend NSView or NSObject
Can you use the passed in layer parameter to construct a switch statement so you can put everything in this method(against the advice of the documents):
-(void) drawLayer: (CALayer*) layer inContext: (CGContextRef) context {
if layer = xLayer {...}
}
Just my 2 cents.

Custom UIView subclass as a UIViewController property

I had thought I actually had a pretty good handle on the whole view controller model, but something just doesn't seem to making much sense for me. My main issue is with adding a custom UIView subclass as a property of a UIViewController subclass.
Whenever I assign a valid instance of a UIView subclass to that property, nothing happens or the code crashes.
Here's a quick outline:
The main controller inits its own view and that loads fine.
I can then add this UIView subclass to the main controller by instantiating it and addSubview:ivar etc. No problems there...
However... if I wanted to have this custom UIView as a property of the ViewController, that does not seem to work. Can anyone shed some light?
Here's a code summary:
#interface CustomUIView : UIView { }
.
#interface MainViewController : UIViewController {
CustomUIView *someOtherView;
}
#property (nonatomic, copy) CustomUIView *someOtherView;
...
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor]; // the default controller view
CustomUIView *tmpView = [[CustomUIView alloc] initWithFrame:CGRectMake(0,0,320,480)];
[self.view addSubview:tmpView]; // this works
self.someOtherView = tmpView; // this does NOT work and
self.view = self.someOtherView; // ultimately, this is what i'm after
[tmpView release];
}
Many thanks to this wonderful community!
You can't copy UIViews. Or UIView subclasses. Try retain, instead.