here's the current situation:
TestViewController.h:
#import <UIKit/UIKit.h>
#interface TestViewController : UIViewController {
}
#end
TestViewController.m:
#import "TestViewController.h"
#import "draw.h"
#implementation TestViewController
- (void)viewDidLoad {
draw.rectColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.6 alpha:1].CGColor;
[draw setNeedsDisplay];
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)dealloc {
[super dealloc];
}
#end
draw.h:
#import <UIKit/UIKit.h>
#interface draw : UIView {
UIColor* rectColor;
}
#property (retain) UIColor* rectColor;
#end
draw.m:
#import "draw.h"
#implementation draw
#synthesize rectColor;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
}
return self;
}
- (void)drawRect:(CGRect)rectangle {
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef myColor = [UIColor colorWithHue:0 saturation:1 brightness:0.61 alpha:1].CGColor;
CGContextSetFillColorWithColor(context, myColor);
CGContextAddRect(context, CGRectMake(0, 0, 95.0, 110.0));
CGContextFillPath(context);
}
- (void)dealloc {
[super dealloc];
}
#end
then in the Interface Builder I've created a 90x115 UIView and set it's Class Identity to "draw".
When I run the application (without the two non-compiling lines) it displays a nice red rectangle in the middle of the screen. My goal is it be able to change the color of the rectangle from within my TestViewController. However when I compile the test app I get the following compiler errors:
error: accessing unknown 'setRectColor:' class method
error: object cannot be set - either readonly property or no setter found
warning: 'draw' may not respond to '+setNeedsDisplay'
I know I am missing "a link" between my TestViewController and draw, but can't figure out how to go about implementing it. I have tried various tricks, but nothing worked.
Could someone please explain what needs to be done in order for
draw.rectColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.6 alpha:1].CGColor;
[draw setNeedsDisplay];
to compile and work ?
(I would use this knowledge to create a different method in TestViewController, I am just testing it inside viewDidLoad)
In the code you posted, you set the rectColor property and call setNeedsDisplay: on draw, but draw is your class. You need to do it to the instance. You shouldn't need to create a new property for this because UIViewController defines the view property. Just use self.view instead of draw in your viewDidLoad method. Also, remove the .CGColor at the end of the line.
Second, your drawRect: method ignores that color and uses its own. Change
CGColorRef myColor = [UIColor colorWithHue:0 saturation:1 brightness:0.61 alpha:1].CGColor;
to
CGColorRef myColor = self.rectColor.CGColor;
The big problem here is that you haven't declared a property for draw; you have to do this and link it up to your Interface Builder object if you want to access it. The solution is to create a property that will hold the draw object, and synthesize its setters and getters using #synthesize. (that last piece is why you're getting the error.)
Once you change the code to what I have below, you'll need to make the proper connection in Interface Builder, or else you'll find that your code changes simply have no effect, even though they're error free.
Capitalize your class names (always!), and then try this:
TestViewController.h:
#import <UIKit/UIKit.h>
#class Draw; //forward class declaration
#interface TestViewController : UIViewController {
IBOutlet Draw *drawCopy;
}
#property (nonatomic, retain) Draw *drawCopy;
#end
Then, for TestViewController.m:
#import "TestViewController.h"
#import "Draw.h"
#implementation TestViewController
#synthesize drawCopy;
-(void) viewDidLoad {
self.drawCopy.rectColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.6 alpha:1];
[self.drawCopy setNeedsDisplay];
[super viewDidLoad];
}
- (void)dealloc {
[drawCopy release];
[super dealloc];
}
#end
I think that should do the trick.
you definde UIColor* rectColor; but u write an CGColor too it
draw.rectColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.6 alpha:1].CGColor;
try
self.drawCopy.rectColor = [UIColor colorWithHue:0.6 saturation:0.6 brightness:0.6 alpha:1];
Related
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)
I'm trying to create a CGRect inside of a custom view (rectView) that will move up and down as you move a slider.
My slider's IBAction calls the following method: (which get's called fine)
- (void)moveRectUpOrDown:(int)y
{
self.verticalPositionOfRect += y;
[self setNeedsDisplay];
}
My drawRect method:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat size = 100;
self.rect = CGRectMake((self.bounds.size.width / 2) - (size / 2),
self.verticalPositionOfRect - (size / 2),
size,
size);
CGContextAddRect(context, self.rect);
CGContextFillPath(context);
}
My custom view's initWithFrame calls the drawRect method using setNeedsDisplay, but for some reason the moveRectUpOrDown won't call drawRect.
Any ideas what I'm doing wrong?
For clarity the entire implementation is below:
//ViewController.h
#import <UIKit/UIKit.h>
#import "rectView.h"
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet rectView *rectView;
- (IBAction)sliderChanged:(id)sender;
#end
//ViewController.m
#import "ViewController.h"
#implementation ViewController
#synthesize rectView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.rectView = [[rectView alloc] initWithFrame:self.rectView.frame];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)sliderChanged:(id)sender
{
UISlider *slider = sender;
CGFloat sliderValue = slider.value;
[self.rectView moveRectUpOrDown:sliderValue];
}
#end
//rectView.h
#import <UIKit/UIKit.h>
#interface rectView : UIView
- (void)moveRectUpOrDown:(int)y;
#end
//rectView.m
#import "rectView.h"
#interface rectView ()
#property CGRect rect;
#property int verticalPositionOfRect;
#end
#implementation rectView
#synthesize rect, verticalPositionOfRect;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.verticalPositionOfRect = (self.bounds.size.height / 2);
[self setNeedsDisplay];
}
return self;
}
- (void)moveRectUpOrDown:(int)y
{
self.verticalPositionOfRect += y;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat size = 100.0;
self.rect = CGRectMake((self.bounds.size.width / 2) - (size / 2),
self.verticalPositionOfRect - (size / 2),
size,
size);
CGContextAddRect(context, self.rect);
CGContextFillPath(context);
}
#end
Thanks for the help :)
OK so the answer is the view is never actually added to the viewController's view..
You have an IBOutlet to your rectView - so I assume you dragged a UIView component onto the view in Interface Builder and then changed it's class to rectView, which is all fine BUT in viewDidLoad you wipe over this object with a new one
self.rectView = [[rectView alloc] initWithFrame:self.rectView.frame];
So this is now no longer the object that was loaded from the xib.
Subsequently this view is never actually added to the view hierarchy so it's never going to draw itself as it does not make sense.
So the solution is to remove the line I highlighted above.
Notes
I would probably still go with my other answer for such an implementation but it's helpful to get tripped up and learn new things.
rectView does not follow naming conventions. Ideally you should start your class name with a 2-3 letter prefix (possibly your initial's or company name) followed by a camel cased named.
I don't see how you initialize CGFloat size. Can you put a value in there and see how it works? The rest of the code looks okay to me.
An easier thing would be to just add a UIView and animate this around.
Add an ivar for this UIView
// .h
#property (nonatomic, strong) UIView *animatedView;
// .m
#synthesize animatedView = _animatedView;
then just replace your logic with this
- (void)moveRectUpOrDown:(int)y;
{
CGRect frame = self.animatedView.frame;
frame.origin.y += y;
// If you want the change to animate uncomment this
// [UIView animateWithDuration:0.25f
// animations:^{
// self.animatedView.frame = frame;
// }];
// If you don't want the change to animate uncomment this
// self.animatedView.frame = frame;
}
I have this super simple example, and I'm not sure why it is not working. drawRect Never gets called. I just want a square to draw and be red. What am I doing wrong?
//Controller.h
#import <Foundation/Foundation.h>
#class CustomView;
#interface Controller : NSObject
#property (nonatomic, retain) CustomView *cv;
#end
//Controller.m
#import "Controller.h"
#import "CustomView.h"
#implementation Controller
#synthesize cv;
- (void) awakeFromNib {
NSLog(#"awakeFromNib called");
CGRect theFrame = CGRectMake(20, 20, 100, 100);
cv = [[CustomView alloc] initWithFrame:theFrame];
UIWindow *theWindow = [[UIApplication sharedApplication] keyWindow];
[theWindow addSubview:cv];
[cv setNeedsDisplay];
}
#end
//CustomView.h
#import <UIKit/UIKit.h>
#interface CustomView : UIView
#end
//CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
NSLog(#"initWithFrame called");
}
return self;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"drawRect called");
self.backgroundColor = [UIColor redColor];
}
#end
You aren't drawing anything in your drawRect. You are just setting a property on the view. If you have overridden drawRect, nothing will be drawn - try calling [super drawRect:rect] (after setting your background colour) or simply draw the square yourself using:
[[UIColor redColor] set];
[[UIBezierPath bezierPathWithRect:self.bounds] fill];
EDIT:
I see your drawRect is not even being called. I'm not sure of your nib structure, but try adding cv as a subview to self.view in your controller rather than adding it to the window. Also, note that you are not retaining cv (use self.cv = rather than cv =) but this shouldn't be an issue since your view will retain it.
Rather than doing a forward reference to your CustomView class in your Controller implementation:
#class CustomView;
Trying importing the class header file:
#import "CustomView.h"
Because you require access to the API you have defined when you call:
cv = [[CustomView alloc] initWithFrame:theFrame];
A forward reference tells the compiler that there will be an implementation for the class you are using at compile time and it is best used in header files. In implementation files, I find it best to import the header.
I'm re-factoring my code and would like to move a whole bunch of UILabels into another class to tidy things up a bit. I'm missing one puzzle piece to be able to do so though (or maybe I'm just tired lol) Anyway here's the simplified code showing my issue. Thanks in advance to anyone who helps :)
#interface MyClass : UIView {
UILabel *classLabel;
}
#property (assign) UILabel *classLabel;
#end
#implementation MyClass
#synthesize classLabel;
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
}
return self;
}
- (void)dealloc {[super dealloc];}
#end
#interface LabelTestViewController : UIViewController {
MyClass *myClassInstance;
UILabel *myLabel;
}
#property (assign) UILabel *myLabel;
#end
#implementation LabelTestViewController
#synthesize myLabel;
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// this shows a label on the screen as expected
myLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 50, 20)];
myLabel.text = #"Hello";
[self.view addSubview:myLabel];
[myLabel release];
// this doesn't show anything on the scree
myClassInstance = [MyClass new];
[myClassInstance drawRect:CGRectMake(10, 50, 50, 20)]; // I suspect I need to call a different method, just don't know which one. initWithFrame is what I used at the time of creation of the label in the previous working scenario. is there an equivalent?
myClassInstance.classLabel.text = #"Goodbye";
[self.view addSubview:myClassInstance.classLabel];
}
- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];}
- (void)viewDidUnload {}
- (void)dealloc {[super dealloc];}
#end
A couple of things:
1) You should never call drawRect directly. Instead, call setNeedsDisplay or setNeedsDisplayInRect. See the Cocoa Drawing Guide or the UIView Class Reference for more info.
2) But that may not be source of your problem. From your code, it is difficult to tell what ends up in classLabel after you are done setting it up, but I expect it's not what you need. In particular, it needs a frame. I would suggest setting a CGRect variable to myClassLabel.frame and seeing what you end up with.
A little background:
I'm a C# developer starting to mess with the iPhone (have an idea for a simple 2D game). The only MVC programming I've done was for the web (ASP.NET MVC) so although I do have an understanding in MVC, I can't wrap my mind around one thing. Here's an example to illustrate.
Say I have a simple app where all I want to do is display a big circle on the screen. I created a "View Based Application" and it gave me the basic classes to start with:
MVCConfusionAppDelegate
MVCConfusionViewController
Now since I'll be doing some custom drawing (I know I can add a subview and show the circle that way, but this is just a sample of a larger piece) I've added a class called MyCustomView and in Interface Builder set the View of the MVCConfusionViewController to be a MyCustomView.
Now here's the problem. I want to be able to set in code the size of how big the ball on the custom view should be. So I have a property on the MyCusomView like this:
#import <Foundation/Foundation.h>
#interface MyCustomView : UIView {
NSNumber *ballSize;
}
#property(nonatomic,retain)IBOutlet NSNumber *ballSize;
#end
#import "MyCustomView.h"
#implementation MyCustomView
#synthesize ballSize;
-(void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor redColor]set];
float floatValue = [self.ballSize floatValue];
CGRect ballRect = CGRectMake(50.0f, 50.0f,floatValue , floatValue);
CGContextFillEllipseInRect(context, ballRect);
}
#end
Then, here's my MVCConfusionViewController:
#import <UIKit/UIKit.h>
#import "MyCustomView.h"
#interface MVCConfusionViewController : UIViewController {
NSNumber *ballSize;
}
#property(nonatomic,retain)IBOutlet NSNumber *ballSize;
#end
#import "MVCConfusionViewController.h"
#import "MyCustomView.h"
#implementation MVCConfusionViewController
#synthesize ballSize;
- (void)viewDidLoad {
[super viewDidLoad];
MyCustomView *myView = (MyCustomView *)self.view;
myView.ballSize = self.ballSize;
}
And finally, the MVCConfusionAppDelegate:
#import <UIKit/UIKit.h>
#class MVCConfusionViewController;
#interface MVCConfusionAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
MVCConfusionViewController *viewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet MVCConfusionViewController *viewController;
#end
#import "MVCConfusionAppDelegate.h"
#import "MVCConfusionViewController.h"
#import "MyCustomView.h"
#implementation MVCConfusionAppDelegate
#synthesize window;
#synthesize viewController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
viewController.ballSize = [NSNumber numberWithInt:200];
[window addSubview:viewController.view];
[window makeKeyAndVisible];
}
- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
#end
As you can see, there's an ugly cast in my viewDidLoad method. I was hoping I'd be able to make the connection of the ballSize properties in IB, but it won't let me.
So my question simply is, what's the correct way of passing this data from my view controller to my view without doing that cast? I know I'm missing something fundamental, but I just don't see it. Any help would be greatly appreciated!
EDIT: Here's the source code. http://bit.ly/uKyp9 Maybe someone can have a look and see if I'm doing anything wrong.
Are you trying to connect one IBOutlet (in the controller) to another IBOutlet (in the view)? Unfortunately, I don't think it's that easy :-)
You're also storing the data (ballSize) in the controller and the view.
I'd make MVCConfusionViewController a data source for MyCustomView, and then let MyCustomView ask its datasource for the ballSize, inside the -drawRect: method.
#class MyCustomView;
#protocol MyCustomViewDataSource
- (NSNumber *)ballSizeForMyCustomView:(MyCustomView *)view;
#end
#interface MyCustomView {
id<MyCustomViewDataSource> dataSource;
}
#property (nonatomic, assign) IBOutlet id<MyCustomViewDataSource> dataSource;
#end
#implementation MyCustomView
- (void)drawRect:(CGRect) rect {
if (self.dataSource == nil) {
// no data source, so we don't know what to draw
return;
}
float floatValue = [[self.dataSource ballSizeForMyCustomView:self] floatValue];
// ...
}
#end
In Interface Builder, hook MVCConfusionViewController up to the view's dataSource property. Then implement the protocol:
#interface MVCConfusionViewController : UIViewController <MyCustomViewDataSource> {
[...]
}
[...]
#end
#implementation MVCConfusionViewController
- (NSNumber *)ballSizeForMyCustomView:(MyCustomView *)view {
return self.ballSize;
}
#end
This way your view controller could also be the data source for multiple MyCustomViews, because the protocol method takes a MyCustomView as an argument.
If you need more than one ball, have a look at the UITableViewDataSource and implement similar methods, something like:
-(NSInteger)numberOfBallsInMyCustomView:(MyCustomView *)view;
-(NSNumber *)myCustomView:(MyCustomView *) ballSizeAtIndex:(NSInteger)index;
Your view should already be set in IB, so you can use it as is. If you want to use MyCustomView, you can do it like this:
- (void)viewDidLoad {
[super viewDidLoad];
CGRect frame = CGRectMake(0, 0, 320, 480);
MyCustomView *myView = [[MyCustomView alloc] initWithFrame:frame];
myView.backgroundColor = [UIColor greenColor];
self.view = myView;
[myView release];
CGRect rectangle = CGRectMake(20, 20, 20, 20);
[self.view drawRect:rectangle];
}
I couldn't make your drawing code work, I don't know much about that.
One way to avoid the cast would be to add a separate outlet property for the custom view on the controller, and refer to that instead.
In Interface Builder, make an instance of MyCustomView and drag it into the existing view to make it a subview, then attach it to its own outlet on the controller.