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
Related
My custom UIView is not triggering this to run, but other custom views I make trigger it. Does anyone know the reason that this automatic refreshing of views would be triggered? Or Why "Refresh All Views" is greyed out.
Code is in here:
https://github.com/HannahCarney/HCRotaryWheel
Make sure you have IBInspectable properties for your view. (like
changing your view color,just to make sure your IBDesignable thing
works).
Make sure you have the code for your customisation in your view .m
file. (in your view draw rect method).
Make sure you provide the file owner name for your IBDesignable view.(in your storyboarD)
Make sure you mention IB_DESIGNABLE on your view .h file.
Finally a qucik clean and build of your storyboard,shall enable the
option of Refresh All views.
Sample code:
for view.h
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface designalble_UIView : UIView
#property (nonatomic) IBInspectable UIColor *startColor;
#property (nonatomic) IBInspectable UIColor *midColor;
#property (nonatomic) IBInspectable UIColor *endColor;
#property (nonatomic) IBInspectable NSInteger borderWidth;
#property (nonatomic) IBInspectable CGFloat cornerRadious;
#property (nonatomic) IBInspectable BOOL isHorizontal;
#end
for view.m
#import "designalble_UIView.h"
#implementation designalble_UIView
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.startColor = [UIColor redColor];
self.midColor = [UIColor greenColor];
self.endColor = [UIColor blueColor];
self.borderWidth = 2;
self.cornerRadious = 10;
self.isHorizontal = NO;
[self customInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self customInit];
}
return self;
}
- (void)drawRect:(CGRect)rect {
[self customInit];
}
- (void)setNeedsLayout {
[super setNeedsLayout];
[self setNeedsDisplay];
}
- (void)prepareForInterfaceBuilder {
[self customInit];
}
- (void)customInit {
self.layer.cornerRadius = self.cornerRadious;
self.layer.borderWidth = self.borderWidth;
if (self.cornerRadious > 0) {
self.layer.masksToBounds = YES;
}
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = self.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[self.startColor CGColor],(id)[self.midColor CGColor], (id)[self.endColor CGColor], nil];
gradient.endPoint = (self.isHorizontal) ? CGPointMake(1, 0) : CGPointMake(0, 1);
[self.layer insertSublayer:gradient atIndex:0];
}
I solved this because I had put IB_DESIGNABLE in the wrong spot in my code:
I had a protocol at the top of my view and previously it looked like
IB_DESIGNABLE
#protocol RotaryProtocol <NSObject>
- (void) wheelDidChangeValue:(int)currentSector;
#end
#interface HCRotaryWheel : UIView
#property (weak) id <RotaryProtocol> delegate;
#property (nonatomic, strong) IBInspectable UIColor* background;
#end
To fix this I had to move IB_DESIGNABLE below protocol
#protocol RotaryProtocol <NSObject>
- (void) wheelDidChangeValue:(int)currentSector;
#end
IB_DESIGNABLE
#interface HCRotaryWheel : UIView
#property (weak) id <RotaryProtocol> delegate;
#property (nonatomic, strong) IBInspectable UIColor* background;
#end
Now my views refresh!!
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;
}
Okay so i started learning UIDynamics and wanted to just work it out on small box.
I declared UIGravityBehaviour and UIDynamicsBehaviour in my ViewController.m and coded the UIView, but when i executed the code, it was not working.
But just as i declared them as #property in my .h file, the program was working completely fine.
What is the reason behind that?
What is the difference between declaring it as #property and without declaring it as #property?
Here are my files.
Before using #property
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* box = [[UIView alloc]initWithFrame:CGRectMake(100, 50, 100, 100)];
box.backgroundColor = [UIColor blackColor];
[self.view addSubview:box];
UIDynamicAnimator* myAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
UIGravityBehavior* gravityBehaviour = [[UIGravityBehavior alloc] initWithItems:#[box]];
[myAnimator addBehavior: gravityBehaviour];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
After Declaring it as #property
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong,nonatomic) UIDynamicAnimator* myAnimator ;
#property (strong,nonatomic) UIGravityBehavior *gravityBehavior;
#property (strong,nonatomic) UICollisionBehavior *collisionBehavior ;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* box = [[UIView alloc]initWithFrame:CGRectMake(100, 50, 100, 100)];
box.backgroundColor = [UIColor blackColor];
[self.view addSubview:box];
self.myAnimator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
self.gravityBehavior = [[UIGravityBehavior alloc] initWithItems:#[box]];
self.collisionBehavior = [[UICollisionBehavior alloc] initWithItems:#[box]];
self.collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
[self.myAnimator addBehavior:self.gravityBehavior];
[self.myAnimator addBehavior:self.collisionBehavior];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You don't have to move UIGravityBehavior to a property, only UIDynamicAnimator to make it work. However, it's probably a good idea anyway in case you want to remove specific gravity behaviours rather than all of them at once.
If you are using ARC:
The UIDynamicAnimator probably does not have a strong reference to the UIGravityBehavior. So after your viewDidLoad method returns, no one owns that UIGravityBehavior and ARC will make sure that that point it is released. When you make a property for it, your viewController owns it, so the UIGravityBehavior will survive until you viewController gets deallocated.
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"