I have recently started learning core graphics to draw on views, but every time I call setNeedsDisplay, the method runs but doesn't trigger drawRect. In my project, I have a view on a view controller. The view contains an if statement in drawRect that checks if a BOOL variable is YES or NO. If YES, it uses the code to draw a red circle. If NO, it draws a blue circle. In the BOOL's setter, it calls setNeedsDisplay. I create an instance of this view class in the ViewController and set the BOOL but the view is not redrawing. Why is the method not getting called? I posted the xcode files and code bits below.
Customview.m:
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
-(void)awakeFromNib{
[self setup];
}
-(void)setup{
_choice1 = YES;
}
-(void)setChoice1:(BOOL)choice1{
_choice1 = choice1;
[self setNeedsDisplay];
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
if (_choice1) {
UIBezierPath * redCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor redColor]setFill];
[redCircle fill];
}else if (!_choice1){
UIBezierPath * blueCircle = [UIBezierPath bezierPathWithOvalInRect:rect];
[[UIColor blueColor]setFill];
[blueCircle fill];
}
}
#end
CustomView.h:
#import <UIKit/UIKit.h>
#interface CustomView : UIView
#property (nonatomic) BOOL choice1;
#end
ViewController.m:
#import "ViewController.h"
#import "CustomView.h"
#interface ViewController ()
- (IBAction)Change:(id)sender;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)Change:(id)sender {
CustomView * cv = [[CustomView alloc]init];
[cv setChoice1:NO];
}
#end
Full project: https://www.mediafire.com/?2c8e5uay00wci0c
Thanks
You need to create an outlet for your CustomView in your ViewController. Delete the code that creates the new CustomView instance and use _cv to refer to the customView.
#interface ViewController ()
- (IBAction)Change:(id)sender;
#property (weak,nonatomic) IBOutlet CustomView *cv;
#end
Then, in the storyboard, connect ViewController's cv outlet to the CustomView. Also, you'll need to implement the initFromCoder: method on CustomView.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self setup];
}
return self;
}
Related
So I seem to have spent the last couple of days trying to find various solutions for this and have decided it's probably best that I just ask someone.
I'm using a storyboard and I send an array from one ViewController to another just fine. But once in this SecondViewController i'm having an issue with it. I would then like to be able to access the array in a subview of this view controller to be able to draw a graph with the data but whenever I try I just always get null from the code in the subview even though it's definitely in the ParentViewController. Could someone please have a look at my current code and let me know how I would get this data in my subview.
ParentViewController.h
#import <UIKit/UIKit.h>
#import "GraphView.h"
#import "Model.h"
#interface GraphViewController : UIViewController
#property (strong) Model *model;
#property (weak) GraphView *graph;
#property (strong) NSArray *data;
#end
ParentViewController.m
#import "GraphViewController.h"
#interface GraphViewController ()
#end
#implementation GraphViewController
#synthesize graph;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Model data: %#",self.data);//prints just fine
[self.graph setNeedsDisplay];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
SubView.h
#import <UIKit/UIKit.h>
#import "Model.h"
#interface GraphView : UIView
#property (weak) NSArray *array;
#property (strong) Model *model;
#end
Subview.m
#import "GraphView.h"
#import "GraphViewController.h"
#implementation GraphView
- (id)initWithFrame:(CGRect)frame dataArray:(NSArray*)data {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSLog(#"Draw Rect");
NSLog(#"%#", self.array);//The bit that keeps returning null
if (self.array != nil){
NSLog(#"Drawing Rect");
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 160, 100);
CGContextAddLineToPoint(ctx,260,300);
CGContextAddLineToPoint(ctx,60,300);
CGContextClosePath(ctx);
[[UIColor blueColor] setFill];
[[UIColor greenColor] setStroke];
CGContextSetLineWidth(ctx,2.0);
CGContextDrawPath(ctx,kCGPathFillStroke);
}
}
Now I know that it would return null now because i'm not actually assigning it to anything but i've tried so many different ways of trying to assign it to the data array in the parent view controller i've forgotten how i even started. Any help would be much appreciated!
Assign GraphView's data in viewDidLoad. Also make sure that your self.graph is properly connected/initialized.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Model data: %#",self.data);//prints just fine
self.graph.array = self.data;
[self.graph setNeedsDisplay];
}
There is 3 issues in your code:
#property (weak) GraphView *graph; // graph is weak, the object will
be released after the assignment.
No assignment for self.array in the GraphView.
Didn't add the graph into the controller.view.subviews.
Here is my code:
#interface GraphViewController : UIViewController
#property (strong) Model *model;
#property (retain) GraphView *graph; // change to retain
#property (strong) NSArray *data;
#end
#implementation GraphViewController
#synthesize graph;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.data = [[NSArray alloc] initWithObjects:#1, #2, nil];
self.graph = [[GraphView alloc] initWithFrame:CGRectMake(0, 0, 320, 320) dataArray:self.data];
[self.view addSubview:self.graph]; // Init graph and add into subviews.
}
#end
#interface GraphView : UIView
- (id)initWithFrame:(CGRect)frame dataArray:(NSArray*)data;
#property (weak) NSArray *array;
#property (strong) Model *model;
#end
#implementation GraphView
- (id)initWithFrame:(CGRect)frame dataArray:(NSArray*)data {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.array = data; // Assign the array.
}
return self;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
NSLog(#"Draw Rect");
NSLog(#"%#", self.array);//The bit that keeps returning null
if (self.array != nil){
NSLog(#"Drawing Rect");
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, 160, 100);
CGContextAddLineToPoint(ctx,260,300);
CGContextAddLineToPoint(ctx,60,300);
CGContextClosePath(ctx);
[[UIColor blueColor] setFill];
[[UIColor greenColor] setStroke];
CGContextSetLineWidth(ctx,2.0);
CGContextDrawPath(ctx,kCGPathFillStroke);
}
}
#end
I can't intercept the init function that's getting called when it's getting created inside of the xib file.
I want to add borderline to it when it gets created so that I won't need to add it manually.
.h:
#import <UIKit/UIKit.h>
#interface UITextView (FITAddBorderline)
- (id) init;
- (id) initWithFrame:(CGRect)frame;
#end
.m:
#import "UITextView+FITAddBorderline.h"
#implementation UITextView (FITAddBorderline)
- (id) init
{
self = [super init];
if (self) {
[self addBorderline];
}
return self;
}
- (id) initWithFrame:(CGRect)frame {
self = [super init];
if (self) {
self.frame = frame;
[self addBorderline];
}
return self;
}
- (void) addBorderline {
//To make the border look very close to a UITextField
[self.layer setBorderColor:[[[UIColor grayColor] colorWithAlphaComponent:0.5] CGColor]];
[self.layer setBorderWidth:2.0];
//The rounded corner part, where you specify your view's corner radius:
self.layer.cornerRadius = 5;
self.clipsToBounds = YES;
}
#end
Views that come from NIBs are initialized with initWithCoder:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self addBorderline];
}
return self;
}
As a side note, I would recommend changing what you are doing and use a subclass instead of a category. You can get yourself into some trouble overriding methods in a category. See more info here.
You just need to implement the awakeFromNib method:
-(void)awakeFromNib
{
[super awakeFromNib];
[self addBorderline];
}
I'm trying to move a point drawn in a UIView based on the value of a UISlider. The code below is for a UIView (subview ?) with a custom class (WindowView) on a UIViewController.
WindowView.h
#import <UIKit/UIKit.h>
#interface WindowView : UIView
- (IBAction)sliderValue:(UISlider *)sender;
#property (weak, nonatomic) IBOutlet UILabel *windowLabel;
#end
WindowView.m
#import "WindowView.h"
#interface WindowView ()
{
float myVal; // I thought my solution was using an iVar but I think I am wrong
}
#end
#implementation WindowView
#synthesize windowLabel;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)sliderValue:(UISlider *)sender
{
myVal = sender.value;
windowLabel.text = [NSString stringWithFormat:#"%f", myVal];
}
- (void)drawRect:(CGRect)rect
{
// I need to get the current value of the slider in drawRect: and update the position of the circle as the slider moves
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(myVal, myVal, 10, 10)];
[circle fill];
}
#end
OK, you need to store the slider value in an instance variable and then force the view to redraw.
WindowView.h:
#import <UIKit/UIKit.h>
#interface WindowView : UIView
{
float _sliderValue; // Current value of the slider
}
// This should be called sliderValueChanged
- (IBAction)sliderValue:(UISlider *)sender;
#property (weak, nonatomic) IBOutlet UILabel *windowLabel;
#end
WindowView.m (modified methods only):
// This should be called sliderValueChanged
- (void)sliderValue:(UISlider *)sender
{
_sliderValue = sender.value;
[self setNeedsDisplay]; // Force redraw
}
- (void)drawRect:(CGRect)rect
{
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(_sliderValue, _sliderValue, 10, 10)];
[circle fill];
}
You probably want to initialise _sliderValue to something useful in the view's init method.
Also _sliderValue probably isn't the name you want to choose; perhaps something like _circleOffset or some such.
I'm building a graphing app, and I have a view and a view controller, which acts as a delegate for the view (to retrieve information). While I haven't started the actual drawing yet, I am currently trying to store values in a dictionary; however, I have certain NSLogs placed methodically across the view controller and I noticed that the delegate methods I call from the view don't get called at all. For example, I call my scaleForGraph function, but it does not execute. Being new to this, I'm not sure if there's something I'm missing. FYI: I have no errors, it compiles and executes. I've tried to slim the code down as much as possible. Thank you for your help!
Here's the .h for my view, where I define the protocol:
// GraphView.h
#import <UIKit/UIKit.h>
#class GraphView;
#protocol GraphViewDelegate <NSObject>
- (float)scaleForGraph:(GraphView *)requestor;
-(NSMutableDictionary*) valuesForGraph:(GraphView *)requestor withWidth:(float) width;
#end
#interface GraphView : UIView
#property (nonatomic) id <GraphViewDelegate> delegate;
#property (nonatomic) id expressionCopy;
#end
And here's the .m:
#import "GraphView.h"
#implementation GraphView
#synthesize expressionCopy = _expressionCopy;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (void)awakeFromNib
{
self.contentMode = UIViewContentModeRedraw;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
CGFloat width = screenSize.width;
CGFloat height = screenSize.height;
float scale = [self.delegate scaleForGraph:self];
NSLog([NSString stringWithFormat:#"My Scale: %f",scale]); //always returns 0
NSMutableDictionary *graphValues = [self.delegate valuesForGraph:self withWidth:width];
}
#end
And here's my view controller .h:
#import <UIKit/UIKit.h>
#import "GraphView.h"
#interface GraphingViewController : UIViewController <GraphViewDelegate>
#property (weak, nonatomic) IBOutlet GraphView *graphView;
#property (strong, nonatomic) IBOutlet UIStepper *stepper;
- (IBAction) changedScale:(UIStepper *)stepper;
#property (nonatomic) int scale;
#property (nonatomic) id expressionCopy;
#end
And here's the .m for the controller:
#import "GraphingViewController.h"
#interface GraphingViewController ()
#end
#implementation GraphingViewController
#synthesize expressionCopy = _expressionCopy;
- (void)updateUI
{
self.stepper.value = self.scale;
[self.graphView setNeedsDisplay];
}
- (void)setScale:(int)scale
{
if (scale < 0) scale = 0;
if (scale > 100) scale = 100;
_scale = scale;
[self updateUI];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(IBAction)changedScale:(UIStepper *)stepper {
self.scale = stepper.value; //this function works fine, but is not delegate/does not get called by view
}
-(float) scaleForGraph:(GraphView *)requestor {
NSLog(#"HI"); //never gets here
}
-(NSMutableDictionary*) valuesForGraph:(GraphView *)requestor withWidth:(float) width {
NSLog(#"Hello2"); //never gets here
}
return xyVals;
}
#end
Nowhere in the code you've posted do you tell your GraphView that the GraphingViewController is it's delegate. So, you are sending a message to nil.
You'll want to do something like:
self.graphView.delegate = self;
In your GraphingViewController setup code.
Make your controller actual delegate of GraphView. You can do it in interface builder by Ctrl-dragging from GraphView to the object (orange circle in the bottom) and than choose "delegate"
I am experiencing problems on my attempt to popup an image in my view.
I have created a new subclass of UIView (UIViewImageOverlay.m) with the image.
UIViewImageOverlay.m
// ImageOverlay.m
// ButtonPopup
//
//
#import "UIViewImageOverlay.h"
#implementation UIViewImageOverlay
- (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
{
CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIImage *myImage = [UIImage imageNamed:#"test.jpeg"];
int w = myImage.size.width;
int h = myImage.size.height;
CGContextDrawImage(contextRef, CGRectMake(0, 0, w, h), myImage.CGImage);
CGContextRelease(contextRef);
}
#end
In my ViewController.m i have a button pushPush to load the view, but the attempt gives a warning
// ViewController.m
// ButtonPopup
//
//
#import "ViewController.h"
#import "UIViewImageOverlay.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize viewImageOverlay;
- (void)viewDidLoad
{
[super viewDidLoad]; // static
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
- (IBAction)pushPush:(id)sender {
[self.view addSubview:self.viewImageOverlay];
}
#end
ViewController.h
// ViewController.h
// ButtonPopup
//
#import <UIKit/UIKit.h>
#class UIViewImageOverlay;
#interface ViewController : UIViewController {
UIViewImageOverlay *viewImageOverlay;
}
#property(nonatomic, retain) UIViewImageOverlay *viewImageOverlay;
- (IBAction)pushPush:(id)sender;
#end
UIViewImageOverlay.h
// ImageOverlay.h
// ButtonPopup
//
//
#import <UIKit/UIKit.h>
#interface UIViewImageOverlay : UIView
#end
[self.view addSubview:self.viewImageOverlay]; reports Incompatible pointer types sending 'UIViewImageOverlay *' to parameter of type 'UIView *'
Thanks in advance.
declare instance variable viewImageOverlay and define it as a property
You have a forward class declaration in your controller's header file (#class UIViewImageOverlay). It shadows the correct implementation.
Include UIViewImageOverlay.h in the implementation file of your controller