ios viewcontroller view graph live with Mutable Array
Good morning,
what I try to achieve is to draw a graph inside a view whilst data are being added live in the VC through an Array.
So far I have a VC and a view with its own class. Inside the class, inside drawRect I generate random numbers and they appear on the screen as dots. So far, so good.
What I want to do is generate the numbers in the ViewController (or get them via an external source), save them in a mutableArray, retrieve them inside the class and use them for the graph.
I found a similar question here on SO, from 2 years ago so there is no point commenting on it. The question was : I have on FirstViewController an array with float value. I need to pass to my View this array
The recommended solution, that was marked as working was :
In your FirstViewController.h write
#include <UIKit/UIKit.h>
extern NSArray *yourArray;
And in your FirstViewController.m write
#import "FirstViewController.h"
@interface FirstViewController ()
@end
NSArray *yourArray;
Now you'll have to import the whole FirstViewController in your "MyView"-Class. Simply #import "FirstViewController.h" in MyView.m. After doing so, "yourArray" will be available in MyView.
I do exactly what it says, then I initialize my Array, put values in it, NSLog in VC to check all working fine and it does. When I try to retrieve one value in my view and NSLog it, it says the value in the array is (null). The code is so simple I almost have nothing to show:
In my VC.h
extern NSMutableArray *yourArray;
In my VC.m implementation
NSMutableArray *yourArray;
In my VC.m viewDidLoad
NSMutableArray *yourArray = [[NSMutableArray alloc] init];
[yourArray insertObject:@"Works" atIndex:0];
NSString *object = [yourArray objectAtIndex:0];
NSLog (@"the item in Array in VC is %@", object);
In my view.m
#import "GraphViewController.h"
And then I tried adding:
NSString *object = [yourArray objectAtIndex:0];
NSLog (@"the item in Array in View is %@", object);
in (void)awakeFromNib, in (id)initWithFrame:(CGRect)frame, in (void)updateValues and in (void)drawRect:(CGRect)rect
It doesn't work from anywhere I put it. I am at a complete loss.
Could somebody please point me in the right direction ?
Thanks,
=============================================
I'm gonna try and go through this bit by bit.
You have: A subclass of UIViewController called GraphViewController. A subclass of UIView called MyGraphView. A storyboard.
In your storyboard you have a UIViewController scene which contains a UIView object. In the "Identity Inspector" you have set the UIViewController scene "Custom Class" field to GraphViewController. Correct? Also in the Identity Inspector you have set the UIView object Custom Class field to MyGraphView. Correct?
So when you right-click-drag from the View object to the header file of GraphViewController the pop up appears ready for you to input a property name and the "Type" field of that popup is already set to be MyGraphView. Correct?
After the IBOutlet property has been set you can select the GraphViewController in storyboard and show the Connections Inspector and see that there exists a referencing Outlet called MyGraphView connected to GraphViewController.?
So if all those are true then your viewController and View are set up in storyboard.
GraphViewController has a property which is an NSMutableArray called yourArray. GraphViewController has a property which is an MyGraphView called myView. MyGraphView has a property which is an NSArray called graphDataArray.
So in your GraphViewController paste this method above -viewDidLoad.
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
self.yourArray = [@[@"Testing"] mutableCopy];
if (self.myView == nil) {
NSLog(@"The Instance of MyGraphView called MyView does not exist");
return;
}else{
NSLog(@"The Instance of MyGraphView called MyView exists %@",self.myView);
}
self.myView.graphDataArray = [self.yourArray copy];
if (self.myView.graphDataArray == nil) {
NSLog(@"GraphDataArray is nil");
}else {
NSLog(@"Object at index 0 = %@",self.myView.graphDataArray[0]);
NSLog(@"MyGraphView can now receive arrays from GraphViewController :]");
}
}
Okay so let's get the array working in the GraphViewController first and worry about the view afterwards.
It is easier to get rid of the code you used from the other stack overflow question and give you something new.
// in GraphViewController.m file
#import "GraphViewController.h"
@interface GraphViewController ()
// create a property that you can access elsewhere using self.yourArray
@property (nonatomic, strong) NSMutableArray *yourArray;
@end
@implementation GraphViewController
-(void)viewDidLoad{
[super viewDidLoad];
self.yourArray = [@[@"Works1",@"Works2",@"StillWorks"] mutableCopy];
NSLog (@"the item in Array in VC is %@", self.yourArray[0]);
NSLog (@"the item in Array in VC is %@", self.yourArray[1]);
NSLog (@"the item in Array in VC is %@", self.yourArray[2]);
}
The View Class
// In AAAView.h declare this property
@property (nonatomic, strong) NSArray *graphDataArray;
Then when you want to pass the array from your ViewController to your view you can just do this
// yourAAAView is the instance of the view you have in your ViewController
yourAAAView.graphDataArray = [self.dataArray copy];
// ***************************************
.
for(int i = 0; i< [self.graphDataArray count]; i++) {
// get object (NSNumber or NSValue) from array
// NSNumber *num = self.graphDataArray[i];
// NSValue *val = self.graphDataArray[i];
// unwrap the value you stored in the object (float or CGpoint etc)
// use those values to draw the dots the same way you did before
}
There's no need to maintain a separate array of points if you're going to render those points to a view. UIBezierPath maintains its own array of points. Instead of passing it an array of points, pass the points to the UIBezierPath object points array.
The following UIView code is all I use to render a graph, live and in real-time (30 times a second), as my Core Data database is updated with new records:
//
// GraphView.h
// DemonNetDAQClient
//
// Created by James Bush on 5/3/17.
// Copyright © 2017 James Bush. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
typedef void(^PointBlock)(id path);
@interface GraphView : UIView
- (void)addPoint:(PointBlock)block;
@end
In the UIView header file, I set up a block that passes a temporary UIBezierPath object to the UIViewController, which will create a path to append to the UIBezierPath that draws the graph in the UIView.
Here's my GraphView.m:
//
// GraphView.m
// DemonNetDAQClient
//
// Created by James Bush on 5/3/17.
// Copyright © 2017 James Bush. All rights reserved.
//
#import "GraphView.h"
#import "GraphViewController.h"
@interface GraphView ()
@property (strong, nonatomic) UIBezierPath *graph;
@end
@implementation GraphView
- (void)drawRect:(CGRect)rect {
[[UIColor redColor]setStroke];
[self.graph stroke];
}
- (void)addPoint:(PointBlock)block {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIBezierPath *graphTmp = [UIBezierPath bezierPath];
[graphTmp moveToPoint:[self.graph currentPoint]];
block(graphTmp);
[self.graph appendPath:graphTmp];
});
}
- (UIBezierPath *)graph {
UIBezierPath *g = self->_graph;
if (!g) {
g = [UIBezierPath bezierPath];
[g moveToPoint:CGPointMake(0.0, CGRectGetMidY(self.frame))];
self->_graph = g;
}
return g;
}
@end
Whenever a new object is inserted into my Core Data database (as detected by my NSFetchedResultsControllerDelegate/UIViewController), the addPoint method is called. When called, the UIViewController is passed a block with a reference to the temporary UIBezierPath object created in the UIView to which it will add a point. When the block is returned to the UIView method, the newly configured, temporary UIBezierPath object is appended to the UIBezierPath that is rendered by the UIView. All your points data is maintained with that UIBezierPath object.
NOTE | I wrote a separate method that creates the UIBezierPath object to set it's first point to the proper location in the graph view, although I could have done this in two lines within the awakeFromNib method. I do this for compatibility with views on storyboards and views instantiated programmatically (you don't call awakeFromNib without a storyboard).
Following is the relevant UIViewController code that gets the point data from the NSFetchedResultsController (which I'm sure you recognize and are already familiar with, having worked with table view controllers—all the same thing):
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert: {
Metric *metric = (Metric *)anObject;
double measurement = metric.metricMeasurement;
CGFloat y = [self convertMetricToPoint:measurement];
[(GraphView *)self.graphView addPoint:^(id path) {
CGPoint newPoint = CGPointMake([path currentPoint].x + 1.0, y);
[path addLineToPoint:newPoint];
[path moveToPoint:newPoint];
}];
break;
}
case NSFetchedResultsChangeDelete: {
break;
}
case NSFetchedResultsChangeUpdate: {
break;
}
case NSFetchedResultsChangeMove: {
break;
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.graphView setNeedsDisplay];
}
- (CGFloat)convertMetricToPoint:(double)metric {
double maxPointValue = ([(NSNumber *)[self valueForKeyPath:@"fetchedResultsController.fetchedObjects.@max.metricMeasurement"] doubleValue]);
double normalizedMetric = (metric / maxPointValue) * self.graphView.frame.size.height;
return (CGFloat)normalizedMetric;
}
Would you like to see this in action? Here's a video:
<iframe width="1280" height="720" src="https://www.youtube.com/embed/JYF2ZSEgOrg" frameborder="0" allowfullscreen></iframe>
链接地址: http://www.djcxy.com/p/14384.html