From 59f25ddaecc790ebc4c8a6aa2d13b5437f70dbbf Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sat, 18 Mar 2017 16:06:42 -0700 Subject: [PATCH 01/15] Add encoder/decoder for BEMLineGraph properties Add averageValue delegate method to allow BEMLineGraph to be separate from BEMCalculator Restore modifyPopUpView delegate method Use caption preferredFont instead of SystemFont fro NoData Handle overlapping labels better Reuse code for popup and permanent labels Bug fixes ensure chart doesn't grow views when zero or one data points --- Classes/BEMSimpleLineGraphView.h | 6 +- Classes/BEMSimpleLineGraphView.m | 789 ++++++++++++++++--------------- 2 files changed, 416 insertions(+), 379 deletions(-) diff --git a/Classes/BEMSimpleLineGraphView.h b/Classes/BEMSimpleLineGraphView.h index 3eec526..fd29532 100644 --- a/Classes/BEMSimpleLineGraphView.h +++ b/Classes/BEMSimpleLineGraphView.h @@ -487,6 +487,10 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView *circleDots; /// The line itself -@property (strong, nonatomic) BEMLine *masterLine; +@property (strong, nonatomic) BEMLine * masterLine; /// The vertical line which appears when the user drags across the graph @property (strong, nonatomic) UIView *touchInputLine; @@ -91,9 +91,8 @@ @interface BEMSimpleLineGraphView () { /// The label displayed when enablePopUpReport is set to YES @property (strong, nonatomic) UILabel *popUpLabel; -/// The label displayed when a custom popUpView is returned by the delegate -@property (strong, nonatomic) UIView *popUpView; - +// Possible custom View displayed instead of popUpLabel +@property (strong, nonatomic) UIView *customPopUpView; #pragma mark calculated properties /// The Y offset necessary to compensate the labels on the X-Axis @@ -108,8 +107,6 @@ @interface BEMSimpleLineGraphView () { /// The smallest value out of all of the data points @property (nonatomic) CGFloat minValue; -// Tracks whether the popUpView is custom or default -@property (nonatomic) BOOL usingCustomPopupView; // Stores the current view size to detect whether a redraw is needed in layoutSubviews @property (nonatomic) CGSize currentViewSize; @@ -117,12 +114,6 @@ @interface BEMSimpleLineGraphView () { /// Find which point is currently the closest to the vertical line - (BEMCircle *)closestDotFromTouchInputLine:(UIView *)touchInputLine; -/// Determines the biggest Y-axis value from all the points -- (CGFloat)maxValue; - -/// Determines the smallest Y-axis value from all the points -- (CGFloat)minValue; - @end @@ -130,16 +121,125 @@ @implementation BEMSimpleLineGraphView #pragma mark - Initialization -- (instancetype)initWithFrame:(CGRect)frame { +- (instancetype) initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) [self commonInit]; return self; } -- (instancetype)initWithCoder:(NSCoder *)coder { +- (instancetype) initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) [self commonInit]; + +#define RestoreProperty(property, type) \ +if ([coder containsValueForKey:@#property]) { \ +self.property = [coder decode ## type ##ForKey:@#property]; \ +}\ + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" + + RestoreProperty (colorXaxisLabel, Object); + RestoreProperty (colorYaxisLabel, Object); + RestoreProperty (colorTop, Object); + RestoreProperty (colorLine, Object); + RestoreProperty (colorBottom, Object); + RestoreProperty (colorPoint, Object); + RestoreProperty (colorTouchInputLine, Object); + RestoreProperty (colorBackgroundPopUplabel, Object); + RestoreProperty (colorBackgroundYaxis, Object); + RestoreProperty (colorBackgroundXaxis, Object); + RestoreProperty (averageLine.color, Object); + + RestoreProperty (alphaTop, Float); + RestoreProperty (alphaLine, Float); + RestoreProperty (alphaTouchInputLine, Float); + RestoreProperty (alphaBackgroundXaxis, Float); + RestoreProperty (alphaBackgroundYaxis, Float); + + RestoreProperty (widthLine, Float); + RestoreProperty (widthReferenceLines, Float); + RestoreProperty (sizePoint, Float); + RestoreProperty (widthTouchInputLine, Float); + + RestoreProperty (enableTouchReport, Bool); + RestoreProperty (enablePopUpReport, Bool); + RestoreProperty (enableBezierCurve, Bool); + RestoreProperty (enableXAxisLabel, Bool); + RestoreProperty (enableYAxisLabel, Bool); + RestoreProperty (autoScaleYAxis, Bool); + RestoreProperty (alwaysDisplayDots, Bool); + RestoreProperty (alwaysDisplayPopUpLabels, Bool); + RestoreProperty (enableLeftReferenceAxisFrameLine, Bool); + RestoreProperty (enableBottomReferenceAxisFrameLine, Bool); + RestoreProperty (interpolateNullValues, Bool); + RestoreProperty (displayDotsOnly, Bool); + RestoreProperty (displayDotsWhileAnimating, Bool); + + RestoreProperty (touchReportFingersRequired, Int); + RestoreProperty (formatStringForValues, Object); + + RestoreProperty (averageLine, Object); return self; +#pragma clang diagnostic pop +} + +- (void) encodeWithEncoder: (NSCoder *)coder { + +#define EncodeProperty(property, type) [coder encode ## type: self.property forKey:@#property] + + + [super encodeWithCoder:coder]; + + EncodeProperty (labelFont, Object); + EncodeProperty (animationGraphEntranceTime, Float); + EncodeProperty (animationGraphStyle, Integer); + EncodeProperty (enableReferenceAxisFrame, Bool); + EncodeProperty (enableTopReferenceAxisFrameLine, Bool); + EncodeProperty (enableRightReferenceAxisFrameLine, Bool); + + EncodeProperty (colorXaxisLabel, Object); + EncodeProperty (colorYaxisLabel, Object); + EncodeProperty (colorTop, Object); + EncodeProperty (colorLine, Object); + EncodeProperty (colorBottom, Object); + EncodeProperty (colorPoint, Object); + EncodeProperty (colorTouchInputLine, Object); + EncodeProperty (colorBackgroundPopUplabel, Object); + EncodeProperty (colorBackgroundYaxis, Object); + EncodeProperty (colorBackgroundXaxis, Object); + EncodeProperty (averageLine.color, Object); + + EncodeProperty (alphaTop, Float); + EncodeProperty (alphaLine, Float); + EncodeProperty (alphaTouchInputLine, Float); + EncodeProperty (alphaBackgroundXaxis, Float); + EncodeProperty (alphaBackgroundYaxis, Float); + + EncodeProperty (widthLine, Float); + EncodeProperty (widthReferenceLines, Float); + EncodeProperty (sizePoint, Float); + EncodeProperty (widthTouchInputLine, Float); + + EncodeProperty (enableTouchReport, Bool); + EncodeProperty (enablePopUpReport, Bool); + EncodeProperty (enableBezierCurve, Bool); + EncodeProperty (enableXAxisLabel, Bool); + EncodeProperty (enableYAxisLabel, Bool); + EncodeProperty (autoScaleYAxis, Bool); + EncodeProperty (alwaysDisplayDots, Bool); + EncodeProperty (alwaysDisplayPopUpLabels, Bool); + EncodeProperty (enableLeftReferenceAxisFrameLine, Bool); + EncodeProperty (enableBottomReferenceAxisFrameLine, Bool); + EncodeProperty (enableTopReferenceAxisFrameLine, Bool); + EncodeProperty (enableRightReferenceAxisFrameLine, Bool); + EncodeProperty (interpolateNullValues, Bool); + EncodeProperty (displayDotsOnly, Bool); + EncodeProperty (displayDotsWhileAnimating, Bool); + + [coder encodeInt: (int)(self.touchReportFingersRequired) forKey:@"touchReportFingersRequired"]; + EncodeProperty (formatStringForValues, Object); + EncodeProperty (averageLine, Object); } - (void)commonInit { @@ -212,17 +312,6 @@ - (void)commonInit { _averageLine = [[BEMAverageLine alloc] init]; } -- (void)prepareForInterfaceBuilder { - // Set points and remove all dots that were previously on the graph - numberOfPoints = 10; - for (UILabel *subview in [self subviews]) { - if ([subview isEqual:self.noDataLabel]) - [subview removeFromSuperview]; - } - - [self drawEntireGraph]; -} - - (void)drawGraph { // Let the delegate know that the graph began layout updates if ([self.delegate respondsToSelector:@selector(lineGraphDidBeginLoading:)]) @@ -256,61 +345,70 @@ - (void)layoutSubviews { [self drawGraph]; } +-(void) clearGraph { + for (UIView * subvView in self.subviews) { + [subvView removeFromSuperview]; + } +} + - (void)layoutNumberOfPoints { // Get the total number of data points from the delegate +#ifndef TARGET_INTERFACE_BUILDER if ([self.dataSource respondsToSelector:@selector(numberOfPointsInLineGraph:)]) { numberOfPoints = [self.dataSource numberOfPointsInLineGraph:self]; + } else { + numberOfPoints = 0; + } +#else + numberOfPoints = 10; +#endif + [self.noDataLabel removeFromSuperview]; + [self.oneDot removeFromSuperview]; - } else numberOfPoints = 0; - - // There are no points to load - if (numberOfPoints == 0) { + if (numberOfPoints == 0) { + // There are no points to load + [self clearGraph]; if (self.delegate && [self.delegate respondsToSelector:@selector(noDataLabelEnableForLineGraph:)] && - ![self.delegate noDataLabelEnableForLineGraph:self]) return; + ![self.delegate noDataLabelEnableForLineGraph:self]) { + return; + } NSLog(@"[BEMSimpleLineGraph] Data source contains no data. A no data label will be displayed and drawing will stop. Add data to the data source and then reload the graph."); - -#ifndef TARGET_INTERFACE_BUILDER self.noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.viewForFirstBaselineLayout.frame.size.width, self.viewForFirstBaselineLayout.frame.size.height)]; -#else - self.noDataLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.viewForFirstBaselineLayout.frame.size.width, self.viewForFirstBaselineLayout.frame.size.height-(self.viewForFirstBaselineLayout.frame.size.height/4))]; -#endif - self.noDataLabel.backgroundColor = [UIColor clearColor]; self.noDataLabel.textAlignment = NSTextAlignmentCenter; - -#ifndef TARGET_INTERFACE_BUILDER - NSString *noDataText; + NSString *noDataText = nil; if ([self.delegate respondsToSelector:@selector(noDataLabelTextForLineGraph:)]) { noDataText = [self.delegate noDataLabelTextForLineGraph:self]; } self.noDataLabel.text = noDataText ?: NSLocalizedString(@"No Data", nil); -#else - self.noDataLabel.text = @"Data is not loaded in Interface Builder"; -#endif - self.noDataLabel.font = self.noDataLabelFont ?: [UIFont systemFontOfSize:15 weight:UIFontWeightRegular]; - self.noDataLabel.textColor = self.noDataLabelColor ?: self.colorLine; + self.noDataLabel.font = self.noDataLabelFont ?: [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; + self.noDataLabel.textColor = self.noDataLabelColor ?: (self.colorLine ?: [UIColor blackColor]); [self.viewForFirstBaselineLayout addSubview:self.noDataLabel]; // Let the delegate know that the graph finished layout updates - if ([self.delegate respondsToSelector:@selector(lineGraphDidFinishLoading:)]) + if ([self.delegate respondsToSelector:@selector(lineGraphDidFinishLoading:)]) { [self.delegate lineGraphDidFinishLoading:self]; + } } else if (numberOfPoints == 1) { NSLog(@"[BEMSimpleLineGraph] Data source contains only one data point. Add more data to the data source and then reload the graph."); + [self clearGraph]; BEMCircle *circleDot = [[BEMCircle alloc] initWithFrame:CGRectMake(0, 0, self.sizePoint, self.sizePoint)]; circleDot.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2); circleDot.color = self.colorPoint; - circleDot.alpha = 1; - [self addSubview:circleDot]; + circleDot.alpha = 1.0f; + + [self.viewForFirstBaselineLayout addSubview:circleDot]; self.oneDot = circleDot; - } else { - // Remove all dots that might have previously been on the graph - [self.noDataLabel removeFromSuperview]; - [self.oneDot removeFromSuperview]; + // Let the delegate know that the graph finished layout updates + if ([self.delegate respondsToSelector:@selector(lineGraphDidFinishLoading:)]) { + [self.delegate lineGraphDidFinishLoading:self]; + } + } } @@ -322,13 +420,12 @@ - (void)layoutTouchReport { self.touchInputLine = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.widthTouchInputLine, self.frame.size.height)]; self.touchInputLine.backgroundColor = self.colorTouchInputLine; self.touchInputLine.alpha = 0; - [self addSubview:self.touchInputLine]; } + [self addSubview:self.touchInputLine]; if (!self.panView) { self.panView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, self.viewForFirstBaselineLayout.frame.size.width, self.viewForFirstBaselineLayout.frame.size.height)]; self.panView.backgroundColor = [UIColor clearColor]; - [self.viewForFirstBaselineLayout addSubview:self.panView]; self.panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGestureAction:)]; self.panGesture.delegate = self; @@ -339,6 +436,7 @@ - (void)layoutTouchReport { self.longPressGesture.minimumPressDuration = 0.1f; [self.panView addGestureRecognizer:self.longPressGesture]; } + [self addSubview:self.panView]; } } @@ -367,9 +465,13 @@ - (void)drawEntireGraph { // Changing the order of the method calls below can result in drawing glitches and even crashes self.averageLine.yValue = NAN; +#ifndef TARGET_INTERFACE_BUILDER self.maxValue = [self getMaximumValue]; self.minValue = [self getMinimumValue]; - +#else + self.minValue = 0.0f; + self.maxValue = 10000.0f; +#endif // Set the Y-Axis Offset if the Y-Axis is enabled. The offset is relative to the size of the longest label on the Y-Axis. if (self.enableYAxisLabel) { self.YAxisLabelXOffset = 2.0f + [self calculateWidestLabel]; @@ -389,30 +491,30 @@ - (void)drawEntireGraph { [self drawYAxis]; } -- (CGFloat)labelWidthForValue:(CGFloat)value { +-(CGFloat) labelWidthForValue:(CGFloat) value { NSDictionary *attributes = @{NSFontAttributeName: self.labelFont}; NSString *valueString = [self yAxisTextForValue:value]; NSString *labelString = [valueString stringByReplacingOccurrencesOfString:@"[0-9-]" withString:@"N" options:NSRegularExpressionSearch range:NSMakeRange(0, [valueString length])]; return [labelString sizeWithAttributes:attributes].width; } -- (CGFloat)calculateWidestLabel { +- (CGFloat) calculateWidestLabel { NSDictionary *attributes = @{NSFontAttributeName: self.labelFont}; CGFloat widestNumber; if (self.autoScaleYAxis == YES){ widestNumber = MAX([self labelWidthForValue:self.maxValue], [self labelWidthForValue:self.minValue]); } else { - widestNumber = [self labelWidthForValue:self.frame.size.height] ; + widestNumber = [self labelWidthForValue:self.frame.size.height] ; } - return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); + return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); } -- (BEMCircle *)circleDotAtIndex:(NSUInteger)index forValue:(CGFloat)dotValue reuseNumber:(NSUInteger)reuseNumber { +-(BEMCircle *) circleDotAtIndex:(NSUInteger) index forValue:(CGFloat) dotValue reuseNumber: (NSUInteger) reuseNumber { CGFloat positionOnXAxis = numberOfPoints > 1 ? - (((self.frame.size.width - self.YAxisLabelXOffset) / (numberOfPoints - 1)) * index) : - self.frame.size.width/2; + (((self.frame.size.width - self.YAxisLabelXOffset) / (numberOfPoints - 1)) * index) : + self.frame.size.width/2; if (self.positionYAxisRight == NO) { positionOnXAxis += self.YAxisLabelXOffset; } @@ -446,14 +548,14 @@ - (BEMCircle *)circleDotAtIndex:(NSUInteger)index forValue:(CGFloat)dotValue reu } - (void)drawDots { + // Remove all data points before adding them to the array [dataPoints removeAllObjects]; // Remove all yAxis values before adding them to the array [yAxisValues removeAllObjects]; - + // Loop through each point and add it to the graph - NSUInteger circleDotNumber = 0; @autoreleasepool { for (NSUInteger index = 0; index < numberOfPoints; index++) { CGFloat dotValue = 0; @@ -462,72 +564,90 @@ - (void)drawDots { if ([self.dataSource respondsToSelector:@selector(lineGraph:valueForPointAtIndex:)]) { dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:index]; - } else [NSException raise:@"lineGraph:valueForPointAtIndex: protocol method is not implemented in the data source. Throwing exception here before the system throws a CALayerInvalidGeometry Exception." format:@"Value for point %f at index %lu is invalid. CALayer position may contain NaN: [0 nan]", dotValue, (unsigned long)index]; + } else { + [NSException raise:@"lineGraph:valueForPointAtIndex: protocol method is not implemented in the data source. Throwing exception here before the system throws a CALayerInvalidGeometry Exception." format:@"Value for point %f at index %lu is invalid. CALayer position may contain NaN: [0 nan]", dotValue, (unsigned long)index]; + } #else dotValue = (int)(arc4random() % 10000); #endif [dataPoints addObject:@(dotValue)]; - BEMCircle *circleDot = [self circleDotAtIndex:index forValue:dotValue reuseNumber:circleDotNumber]; + BEMCircle * circleDot = [self circleDotAtIndex: index forValue: dotValue reuseNumber: index]; + UILabel * label = nil; if (circleDot) { - if (!circleDot.superview) { - [self addSubview:circleDot]; - } + [self addSubview:circleDot]; if (self.alwaysDisplayPopUpLabels == YES) { if (![self.delegate respondsToSelector:@selector(lineGraph:alwaysDisplayPopUpAtIndex:)] || [self.delegate lineGraph:self alwaysDisplayPopUpAtIndex:index]) { - UILabel *label = (UILabel *)[self labelForPoint:circleDot reuseNumber:circleDotNumber]; - if (label && !label.superview) { + if (index < self.permanentPopups.count) { + label = self.permanentPopups[index]; + } else { + label = [[UILabel alloc] initWithFrame:CGRectZero]; + [self.permanentPopups addObject:label ]; + } + label = [self configureLabel:label forPoint: circleDot ]; + + [self adjustXLocForLabel:label avoidingDot:circleDot.frame]; + + UILabel * leftNeighbor = (index >= 1 && self.permanentPopups[index-1].superview) ? self.permanentPopups[index-1] : nil; + UILabel * secondNeighbor = (index >= 2 && self.permanentPopups[index-2].superview) ? self.permanentPopups[index-2] : nil; + BOOL showLabel = [self adjustYLocForLabel:label + avoidingDot:circleDot.frame + andNeighbors:leftNeighbor.frame + and:secondNeighbor.frame ]; + if (showLabel) { [self addSubview:label]; + } else { + [label removeFromSuperview]; } } } - // Dot entrance animation - circleDot.alpha = 0; + // Dot and/or label entrance animation + circleDot.alpha = 0.0f; + label.alpha = 0.0f; if (self.animationGraphEntranceTime <= 0) { if (self.displayDotsOnly || self.alwaysDisplayDots ) { - circleDot.alpha = 1.0; - } - } else { - if (self.displayDotsWhileAnimating) { - [UIView animateWithDuration: self.animationGraphEntranceTime/numberOfPoints delay: index*(self.animationGraphEntranceTime/numberOfPoints) options:UIViewAnimationOptionCurveLinear animations:^{ - circleDot.alpha = 1.0; - } completion:^(BOOL finished) { - if (self.alwaysDisplayDots == NO && self.displayDotsOnly == NO) { - [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ - circleDot.alpha = 0; - } completion:nil]; - } - }]; + circleDot.alpha = 1.0f; } + label.alpha = 1.0f; + } else if (self.displayDotsWhileAnimating) { + [UIView animateWithDuration: self.animationGraphEntranceTime/numberOfPoints delay: index*(self.animationGraphEntranceTime/numberOfPoints) options:UIViewAnimationOptionCurveLinear animations:^{ + circleDot.alpha = 1.0; + label.alpha = 1.0; + } completion:^(BOOL finished) { + if (self.alwaysDisplayDots == NO && self.displayDotsOnly == NO) { + [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + circleDot.alpha = 0; + } completion:nil]; + } + }]; + } else if (label) { + [UIView animateWithDuration:0.5f delay:self.animationGraphEntranceTime options:UIViewAnimationOptionCurveLinear animations:^{ + label.alpha = 1; + } completion:nil]; } - circleDotNumber++; + } } - - for (NSUInteger i = self.circleDots.count -1; i>=circleDotNumber; i--) { - [[self.permanentPopups lastObject] removeFromSuperview]; //no harm if not created + for (NSUInteger i = self.circleDots.count -1; i>=numberOfPoints; i--) { + [[self.permanentPopups lastObject] removeFromSuperview]; //no harm if not showing [self.permanentPopups removeLastObject]; [[self.circleDots lastObject] removeFromSuperview]; [self.circleDots removeLastObject]; } - - // Remove popups - [self.popUpLabel removeFromSuperview]; - [self.popUpView removeFromSuperview]; } } - (void)drawLine { if (!self.masterLine) { self.masterLine = [[BEMLine alloc] initWithFrame:[self drawableGraphArea]]; - [self addSubview:self.masterLine]; } else { self.masterLine.frame = [self drawableGraphArea]; [self.masterLine setNeedsDisplay]; } + [self addSubview:self.masterLine]; BEMLine * line = self.masterLine; line.opaque = NO; line.alpha = 1; @@ -569,11 +689,10 @@ - (void)drawLine { line.animationType = self.animationGraphStyle; if (self.averageLine.enableAverageLine == YES) { - NSNumber *average = [[BEMGraphCalculator sharedCalculator] calculatePointValueAverageOnGraph:self]; - if (isnan(self.averageLine.yValue)) self.averageLine.yValue = average.floatValue; + if (isnan(self.averageLine.yValue)) self.averageLine.yValue = self.getAverageValue; line.averageLineYCoordinate = [self yPositionForDotValue:self.averageLine.yValue]; - line.averageLine = self.averageLine; - } else line.averageLine = self.averageLine; + } + line.averageLine = self.averageLine; line.disableMainLine = self.displayDotsOnly; @@ -596,15 +715,18 @@ - (void)drawXAxis { if (![self.dataSource respondsToSelector:@selector(lineGraph:labelOnXAxisForIndex:)]) return; [xAxisValues removeAllObjects]; + [xAxisLabelPoints removeAllObjects]; + xAxisHorizontalFringeNegationValue = 0.0; // Draw X-Axis Background Area if (!self.backgroundXAxis) { self.backgroundXAxis = [[UIView alloc] initWithFrame:[self drawableXAxisArea]]; - [self addSubview:self.backgroundXAxis]; } else { self.backgroundXAxis.frame = [self drawableXAxisArea]; } + [self addSubview:self.backgroundXAxis]; + self.backgroundXAxis.backgroundColor = self.colorBackgroundXaxis ?: self.colorBottom; self.backgroundXAxis.alpha = self.alphaBackgroundXaxis; @@ -612,27 +734,22 @@ - (void)drawXAxis { if ([self.delegate respondsToSelector:@selector(incrementPositionsForXAxisOnLineGraph:)]) { axisIndices = [self.delegate incrementPositionsForXAxisOnLineGraph:self]; } else { - NSUInteger baseIndex; - NSUInteger increment; + NSUInteger baseIndex = 0; + NSUInteger increment = 1; if ([self.delegate respondsToSelector:@selector(baseIndexForXAxisOnLineGraph:)] && [self.delegate respondsToSelector:@selector(incrementIndexForXAxisOnLineGraph:)]) { baseIndex = [self.delegate baseIndexForXAxisOnLineGraph:self]; increment = [self.delegate incrementIndexForXAxisOnLineGraph:self]; - } else { - if ([self.delegate respondsToSelector:@selector(numberOfGapsBetweenLabelsOnLineGraph:)]) { - increment = [self.delegate numberOfGapsBetweenLabelsOnLineGraph:self] + 1; - if (increment >= numberOfPoints -1) { - //need at least two points - baseIndex = 0; - increment = numberOfPoints - 1; - } else { - NSUInteger leftGap = increment - 1; - NSUInteger rightGap = numberOfPoints % increment; - NSUInteger offset = (leftGap-rightGap)/2; - baseIndex = increment - 1 - offset; - } - } else { - increment = 1; + } else if ([self.delegate respondsToSelector:@selector(numberOfGapsBetweenLabelsOnLineGraph:)]) { + increment = [self.delegate numberOfGapsBetweenLabelsOnLineGraph:self] + 1; + if (increment >= numberOfPoints -1) { + //need at least two points baseIndex = 0; + increment = numberOfPoints - 1; + } else { + NSUInteger leftGap = increment - 1; + NSUInteger rightGap = numberOfPoints % increment; + NSUInteger offset = (leftGap-rightGap)/2; + baseIndex = increment - 1 - offset; } } NSMutableArray *values = [NSMutableArray array ]; @@ -648,18 +765,19 @@ - (void)drawXAxis { @autoreleasepool { for (NSNumber *indexNum in axisIndices) { NSUInteger index = indexNum.unsignedIntegerValue; + if (index > numberOfPoints) continue; NSString *xAxisLabelText = [self xAxisTextForIndex:index]; UILabel *labelXAxis = [self xAxisLabelWithText:xAxisLabelText atIndex:index reuseNumber: xAxisLabelNumber]; - [xAxisLabelPoints addObject:@(labelXAxis.center.x - (self.positionYAxisRight ? self.YAxisLabelXOffset : 0.0f))]; + [xAxisLabelPoints addObject:@(labelXAxis.center.x - (self.positionYAxisRight ? 0.0f : self.YAxisLabelXOffset))]; - if (!labelXAxis.superview) [self addSubview:labelXAxis]; + [self addSubview:labelXAxis]; [xAxisValues addObject:xAxisLabelText]; xAxisLabelNumber++; } } - for (NSUInteger i = self.xAxisLabels.count -1; i>=xAxisLabelNumber; i--) { + for (NSUInteger i = self.xAxisLabels.count ; i>xAxisLabelNumber; i--) { [[self.xAxisLabels lastObject] removeFromSuperview]; [self.xAxisLabels removeLastObject]; } @@ -697,7 +815,7 @@ - (NSString *)xAxisTextForIndex:(NSUInteger)index { if ([self.dataSource respondsToSelector:@selector(lineGraph:labelOnXAxisForIndex:)]) { xAxisLabelText = [self.dataSource lineGraph:self labelOnXAxisForIndex:index]; - } else { + } else { xAxisLabelText = @""; } @@ -725,12 +843,12 @@ - (UILabel *)xAxisLabelWithText:(NSString *)text atIndex:(NSUInteger)index reuse // Determine the horizontal translation to perform on the far left and far right labels // This property is negated when calculating the position of reference frames - CGFloat horizontalTranslation; + CGFloat horizontalTranslation = 0; if (index == 0) { horizontalTranslation = lRect.size.width/2; } else if (index+1 == numberOfPoints) { horizontalTranslation = -lRect.size.width/2; - } else horizontalTranslation = 0; + } xAxisHorizontalFringeNegationValue = horizontalTranslation; // Determine the final x-axis position @@ -744,7 +862,7 @@ - (UILabel *)xAxisLabelWithText:(NSString *)text atIndex:(NSUInteger)index reuse return labelXAxis; } -- (NSString *)yAxisTextForValue:(CGFloat)value { +-(NSString *) yAxisTextForValue:(CGFloat) value { NSString *yAxisSuffix = @""; NSString *yAxisPrefix = @""; @@ -752,7 +870,7 @@ - (NSString *)yAxisTextForValue:(CGFloat)value { if ([self.delegate respondsToSelector:@selector(yAxisSuffixOnLineGraph:)]) yAxisSuffix = [self.delegate yAxisSuffixOnLineGraph:self]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" - NSString *formattedValue = [NSString stringWithFormat:self.formatStringForValues, value]; + NSString *formattedValue = [NSString stringWithFormat:self.formatStringForValues, value]; #pragma clang diagnostic pop return [NSString stringWithFormat:@"%@%@%@", yAxisPrefix, formattedValue, yAxisSuffix]; @@ -768,11 +886,11 @@ - (UILabel *)yAxisLabelWithText:(NSString *)text atValue:(CGFloat)value reuseNum NSTextAlignment textAlignmentForLabelYAxis = NSTextAlignmentRight; if (self.positionYAxisRight) { frameForLabelYAxis.origin = CGPointMake(self.frame.size.width - self.YAxisLabelXOffset - 1.0f, 0.0f); - xValueForCenterLabelYAxis = self.frame.size.width - xValueForCenterLabelYAxis; + xValueForCenterLabelYAxis = self.frame.size.width - xValueForCenterLabelYAxis-2.0f; } UILabel *labelYAxis; - if (reuseNumber == NSIntegerMax) { + if ( reuseNumber == NSIntegerMax) { if (!self.averageLine.label) { self.averageLine.label = [[UILabel alloc] initWithFrame:frameForLabelYAxis]; } @@ -783,6 +901,7 @@ - (UILabel *)yAxisLabelWithText:(NSString *)text atValue:(CGFloat)value reuseNum labelYAxis = [[UILabel alloc] initWithFrame:frameForLabelYAxis]; [self.yAxisLabels addObject:labelYAxis]; } + labelYAxis.frame = frameForLabelYAxis; labelYAxis.text = text; labelYAxis.textAlignment = textAlignmentForLabelYAxis; labelYAxis.font = self.labelFont; @@ -800,6 +919,8 @@ - (void)drawYAxis { if (!self.enableYAxisLabel) { [self.backgroundYAxis removeFromSuperview]; self.backgroundYAxis = nil; + [self.averageLine.label removeFromSuperview]; + self.averageLine.label = nil; for (UILabel * label in self.yAxisLabels) { [label removeFromSuperview]; } @@ -810,18 +931,18 @@ - (void)drawYAxis { //Make Background for Y Axis CGRect frameForBackgroundYAxis = CGRectMake( (self.positionYAxisRight ? - self.frame.size.width - self.YAxisLabelXOffset - 1: - 1), + self.frame.size.width - self.YAxisLabelXOffset - 1.0f: + 0.0), 0, - self.YAxisLabelXOffset -1, + self.YAxisLabelXOffset - 1.0f, self.frame.size.height); if (!self.backgroundYAxis) { self.backgroundYAxis= [[UIView alloc] initWithFrame:frameForBackgroundYAxis]; - [self addSubview:self.backgroundYAxis]; } else { self.backgroundYAxis.frame = frameForBackgroundYAxis; } + [self addSubview:self.backgroundYAxis]; self.backgroundYAxis.backgroundColor = self.colorBackgroundYaxis ?: self.colorTop; self.backgroundYAxis.alpha = self.alphaBackgroundYaxis; @@ -838,21 +959,17 @@ - (void)drawYAxis { CGFloat increment; if (self.autoScaleYAxis) { // Plot according to min-max range - NSNumber *minimumNumber = [[BEMGraphCalculator sharedCalculator] calculateMinimumPointValueOnGraph:self]; - NSNumber *maximumNumber = [[BEMGraphCalculator sharedCalculator] calculateMaximumPointValueOnGraph:self]; - CGFloat minValue = minimumNumber.floatValue; - CGFloat maxValue = maximumNumber.floatValue; if (numberOfLabels == 1) { - value = (minValue + maxValue)/2.0f; + value = (self.minValue + self.maxValue)/2.0f; increment = 0; //NA } else { - value = minValue; - increment = (maxValue - minValue)/(numberOfLabels-1); + value = self.minValue; + increment = (self.maxValue - self.minValue)/(numberOfLabels-1); if ([self.delegate respondsToSelector:@selector(baseValueForYAxisOnLineGraph:)] && [self.delegate respondsToSelector:@selector(incrementValueForYAxisOnLineGraph:)]) { value = [self.delegate baseValueForYAxisOnLineGraph:self]; increment = [self.delegate incrementValueForYAxisOnLineGraph:self]; - numberOfLabels = (NSUInteger) ((maxValue - value)/increment)+1; + numberOfLabels = (NSUInteger) ((self.maxValue - value)/increment)+1; if (numberOfLabels > 100) { NSLog(@"[BEMSimpleLineGraph] Increment does not properly lay out Y axis, bailing early"); return; @@ -884,7 +1001,7 @@ - (void)drawYAxis { atValue:dotValue reuseNumber:yAxisLabelNumber]; - if (!labelYAxis.superview) [self addSubview:labelYAxis]; + [self addSubview:labelYAxis]; yAxisLabelNumber++; } } @@ -904,17 +1021,17 @@ - (void)drawYAxis { prevLabel = label; //always show first label } else if (label.superview) { //only look at active labels if (CGRectIsNull(CGRectIntersection(prevLabel.frame, label.frame)) && - CGRectContainsRect(self.backgroundYAxis.bounds, label.frame)) { + CGRectContainsRect(self.backgroundYAxis.frame, label.frame)) { prevLabel = label; //no overlap and inside frame, so show this one } else { [overlapLabels addObject:label]; // Overlapped -// NSLog(@"Not showing %@ due to %@; label: %@, width: %@ prevLabel: %@, frame: %@", -// label.text, -// CGRectIsNull(CGRectIntersection(prevLabel.frame, label.frame)) ?@"Overlap" : @"Out of bounds", -// NSStringFromCGRect(label.frame), -// @(CGRectGetMaxX(label.frame)), -// NSStringFromCGRect(prevLabel.frame), -// NSStringFromCGRect(self.backgroundXAxis.frame)); + // NSLog(@"Not showing %@ due to %@; label: %@, width: %@ prevLabel: %@, frame: %@", + // label.text, + // CGRectIsNull(CGRectIntersection(prevLabel.frame, label.frame)) ?@"Overlap" : @"Out of bounds", + // NSStringFromCGRect(label.frame), + // @(CGRectGetMaxX(label.frame)), + // NSStringFromCGRect(prevLabel.frame), + // NSStringFromCGRect(self.backgroundXAxis.frame)); } } }]; @@ -923,10 +1040,10 @@ - (void)drawYAxis { if (self.averageLine.enableAverageLine && self.averageLine.title.length > 0) { UILabel *averageLabel = [self yAxisLabelWithText:self.averageLine.title - atValue:self.averageLine.yValue - reuseNumber:NSIntegerMax]; + atValue:self.averageLine.yValue + reuseNumber:NSIntegerMax]; - if (!averageLabel.superview) [self addSubview:averageLabel]; + [self addSubview:averageLabel]; //check for overlap; Average wins for (UILabel * label in self.yAxisLabels) { @@ -944,9 +1061,9 @@ - (void)drawYAxis { } /// Area on the graph that doesn't include the axes -- (CGRect)drawableGraphArea { +- (CGRect) drawableGraphArea { // CGRectMake(xAxisXPositionFirstOffset, self.frame.size.height-20, viewWidth/2, 20); - CGFloat xAxisHeight = self.labelFont.pointSize + 8.0f; + CGFloat xAxisHeight = self.enableXAxisLabel ? self.labelFont.pointSize + 8.0f : 0.0f; CGFloat xOrigin = self.positionYAxisRight ? 0 : self.YAxisLabelXOffset; CGFloat viewWidth = self.frame.size.width - self.YAxisLabelXOffset; CGFloat adjustedHeight = self.bounds.size.height - xAxisHeight; @@ -963,164 +1080,38 @@ - (CGRect)drawableXAxisArea { return CGRectMake(xAxisXOrigin, xAxisYOrigin, xAxisWidth, xAxisHeight); } -- (UIView *)labelForPoint:(BEMCircle *)circleDot reuseNumber:(NSUInteger)reuseNumber { - // If the reuse number is NSIntegerMax, then we've got a popup from a touch gesture. - BOOL touchPopUp = reuseNumber == NSIntegerMax; - NSUInteger index = (NSUInteger) circleDot.tag - DotFirstTag100; +- (UILabel *)configureLabel: (UILabel *) oldLabel forPoint: (BEMCircle *)circleDot { - if (touchPopUp) { - // If the popup is from a touch gesture, simply add it to our view hierarchy. - if (!self.popUpLabel) self.popUpLabel = [[UILabel alloc] init]; - } else return [self permanentLabel:circleDot reuseNumber:reuseNumber]; - - // Check if the delegate has provided a custom popup view - if ([self.delegate respondsToSelector:@selector(popUpViewForLineGraph:)]) { - // The delegate has provided a custom popup. Lets retrieve it and - if (!self.popUpView) self.popUpView = [self.delegate popUpViewForLineGraph:self]; - self.usingCustomPopupView = YES; - - // Fix any left / right layout issues - CGFloat xCenter = circleDot.center.x; - CGFloat halfLabelWidth = self.popUpView.frame.size.width/2 ; - if (self.enableYAxisLabel && !self.positionYAxisRight && ((xCenter - halfLabelWidth) <= self.YAxisLabelXOffset) ) { - // When bumping into left Y axis - xCenter = halfLabelWidth + self.YAxisLabelXOffset + 4.0f; - } else if (self.enableYAxisLabel && self.positionYAxisRight && (xCenter + halfLabelWidth >= self.frame.size.width - self.YAxisLabelXOffset)) { - // When bumping into right Y axis - xCenter = self.frame.size.width - halfLabelWidth - self.YAxisLabelXOffset - 4.0f; - } else if (xCenter - halfLabelWidth <= 0) { - // When over left edge - xCenter = halfLabelWidth + 4.0f; - } else if (xCenter + halfLabelWidth >= self.frame.size.width) { - // When over right edge - xCenter = self.frame.size.width - halfLabelWidth; - } - - // Now set the Y directions. The default is over point. - CGFloat halfLabelHeight = self.popUpView.frame.size.height/2.0f; - CGFloat yCenter = circleDot.frame.origin.y - 2.0f - halfLabelHeight; - self.popUpView.center = CGPointMake(xCenter, yCenter); - - if (!self.popUpView.superview) { - self.popUpView.alpha = 0.0f; - - [UIView animateWithDuration:0.2f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ - [self addSubview:self.popUpView]; - self.popUpView.alpha = 1.0f; - } completion:nil]; - } - - return self.popUpView; - } else { - self.usingCustomPopupView = NO; - - // Set the basic parameters for the popup label - self.popUpLabel.textAlignment = NSTextAlignmentCenter; - self.popUpLabel.numberOfLines = 0; - self.popUpLabel.font = self.labelFont; - self.popUpLabel.backgroundColor = [UIColor clearColor]; - self.popUpLabel.layer.backgroundColor = [self.colorBackgroundPopUplabel colorWithAlphaComponent:0.7f].CGColor; - self.popUpLabel.layer.cornerRadius = 6; - - // Populate the popup label text with values - self.popUpLabel.text = nil; - if ([self.delegate respondsToSelector:@selector(popUpTextForlineGraph:atIndex:)]) self.popUpLabel.text = [self.delegate popUpTextForlineGraph:self atIndex:index]; - - // If the supplied popup label text is nil we can proceed to fill out the text using suffixes, prefixes, and the graph's data source. - if (self.popUpLabel.text == nil) { - NSString *prefix = @""; - NSString *suffix = @""; - - if ([self.delegate respondsToSelector:@selector(popUpSuffixForlineGraph:)]) - suffix = [self.delegate popUpSuffixForlineGraph:self]; - - if ([self.delegate respondsToSelector:@selector(popUpPrefixForlineGraph:)]) - prefix = [self.delegate popUpPrefixForlineGraph:self]; - - NSNumber *value = (index <= dataPoints.count) ? value = dataPoints[index] : @(0); // @((NSInteger) circleDot.absoluteValue) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wformat-nonliteral" - NSString *formattedValue = [NSString stringWithFormat:self.formatStringForValues, value.doubleValue]; -#pragma clang diagnostic pop - self.popUpLabel.text = [NSString stringWithFormat:@"%@%@%@", prefix, formattedValue, suffix]; - } - - // Set the size and frame of the popup - [self.popUpLabel sizeToFit]; - self.popUpLabel.frame = CGRectMake(0, 0, self.popUpLabel.frame.size.width + 7, self.popUpLabel.frame.size.height + 2); - - // Fix any left / right layout issues - CGFloat xCenter = circleDot.center.x; - CGFloat halfLabelWidth = self.popUpLabel.frame.size.width/2 ; - if (self.enableYAxisLabel && !self.positionYAxisRight && ((xCenter - halfLabelWidth) <= self.YAxisLabelXOffset) ) { - // When bumping into left Y axis - xCenter = halfLabelWidth + self.YAxisLabelXOffset + 4.0f; - } else if (self.enableYAxisLabel && self.positionYAxisRight && (xCenter + halfLabelWidth >= self.frame.size.width - self.YAxisLabelXOffset)) { - // When bumping into right Y axis - xCenter = self.frame.size.width - halfLabelWidth - self.YAxisLabelXOffset - 4.0f; - } else if (xCenter - halfLabelWidth <= 0) { - // When over left edge - xCenter = halfLabelWidth + 4.0f; - } else if (xCenter + halfLabelWidth >= self.frame.size.width) { - // When over right edge - xCenter = self.frame.size.width - halfLabelWidth; - } - - // Now set the Y directions. The default is over point. - CGFloat halfLabelHeight = self.popUpLabel.frame.size.height/2.0f; - CGFloat yCenter = circleDot.frame.origin.y - 2.0f - halfLabelHeight; - self.popUpLabel.center = CGPointMake(xCenter, yCenter); - - if (!self.popUpLabel.superview) { - self.popUpLabel.alpha = 0.0f; - - [UIView animateWithDuration:0.2f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ - [self addSubview:self.popUpLabel]; - self.popUpLabel.alpha = 1.0f; - } completion:nil]; - } - - return self.popUpLabel; + UILabel *newPopUpLabel = oldLabel; + if ( !newPopUpLabel) { + newPopUpLabel =[[UILabel alloc] init]; + newPopUpLabel.alpha = 0; } -} -- (UIView *)permanentLabel:(BEMCircle *)circleDot reuseNumber:(NSUInteger)reuseNumber { - // If the reuse number is NSIntegerMax, then we've got a popup from a touch gesture. - NSUInteger index = (NSUInteger) circleDot.tag - DotFirstTag100; - - UILabel *newPopUpLabel; - if (reuseNumber < self.permanentPopups.count) { - newPopUpLabel = self.permanentPopups[reuseNumber]; - } else { - newPopUpLabel = [[UILabel alloc] init]; - [self.permanentPopups addObject:newPopUpLabel]; - } - - self.usingCustomPopupView = NO; - - // Set the basic parameters for the popup label newPopUpLabel.textAlignment = NSTextAlignmentCenter; newPopUpLabel.numberOfLines = 0; newPopUpLabel.font = self.labelFont; newPopUpLabel.backgroundColor = [UIColor clearColor]; newPopUpLabel.layer.backgroundColor = [self.colorBackgroundPopUplabel colorWithAlphaComponent:0.7f].CGColor; newPopUpLabel.layer.cornerRadius = 6; - + + NSUInteger index = (NSUInteger) circleDot.tag - DotFirstTag100; + // Populate the popup label text with values newPopUpLabel.text = nil; if ([self.delegate respondsToSelector:@selector(popUpTextForlineGraph:atIndex:)]) newPopUpLabel.text = [self.delegate popUpTextForlineGraph:self atIndex:index]; - + // If the supplied popup label text is nil we can proceed to fill out the text using suffixes, prefixes, and the graph's data source. if (newPopUpLabel.text == nil) { NSString *prefix = @""; NSString *suffix = @""; - + if ([self.delegate respondsToSelector:@selector(popUpSuffixForlineGraph:)]) suffix = [self.delegate popUpSuffixForlineGraph:self]; - + if ([self.delegate respondsToSelector:@selector(popUpPrefixForlineGraph:)]) prefix = [self.delegate popUpPrefixForlineGraph:self]; - + NSNumber *value = (index <= dataPoints.count) ? value = dataPoints[index] : @(0); // @((NSInteger) circleDot.absoluteValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" @@ -1128,62 +1119,59 @@ - (UIView *)permanentLabel:(BEMCircle *)circleDot reuseNumber:(NSUInteger)reuseN #pragma clang diagnostic pop newPopUpLabel.text = [NSString stringWithFormat:@"%@%@%@", prefix, formattedValue, suffix]; } - - // Set the size and frame of the popup - [newPopUpLabel sizeToFit]; - newPopUpLabel.frame = CGRectMake(0, 0, newPopUpLabel.frame.size.width + 7, newPopUpLabel.frame.size.height + 2); - - // Fix any left / right layout issues - CGFloat xCenter = circleDot.center.x; - CGFloat halfLabelWidth = newPopUpLabel.frame.size.width/2 ; + NSLog(@"%@ before SizeToFit: %@",newPopUpLabel.text, NSStringFromCGRect(newPopUpLabel.frame)); + CGSize requiredSize = [newPopUpLabel sizeThatFits:CGSizeMake(100.0f, CGFLOAT_MAX)]; + newPopUpLabel.frame = CGRectMake(10, 10, requiredSize.width+10.0f, requiredSize.height+10.0f); + NSLog(@"%@ after SizeToFit: %@",newPopUpLabel.text, NSStringFromCGRect(newPopUpLabel.frame)); + return newPopUpLabel; +} + +-(void) adjustXLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) circleDotFrame { + + //now fixup left/right layout issues + CGFloat xCenter = CGRectGetMidX(circleDotFrame); + CGFloat halfLabelWidth = popUpLabel.frame.size.width/2 ; if (self.enableYAxisLabel && !self.positionYAxisRight && ((xCenter - halfLabelWidth) <= self.YAxisLabelXOffset) ) { - // When bumping into left Y axis + //When bumping into left Y axis xCenter = halfLabelWidth + self.YAxisLabelXOffset + 4.0f; } else if (self.enableYAxisLabel && self.positionYAxisRight && (xCenter + halfLabelWidth >= self.frame.size.width - self.YAxisLabelXOffset)) { - // When bumping into right Y axis + //When bumping into right Y axis xCenter = self.frame.size.width - halfLabelWidth - self.YAxisLabelXOffset - 4.0f; } else if (xCenter - halfLabelWidth <= 0) { - // When over left edge + //When over left edge xCenter = halfLabelWidth + 4.0f; } else if (xCenter + halfLabelWidth >= self.frame.size.width) { - // When over right edge + //When over right edge xCenter = self.frame.size.width - halfLabelWidth; } - - // Now set the Y directions. The default is over point. - CGFloat halfLabelHeight = newPopUpLabel.frame.size.height/2.0f; - CGFloat yCenter = circleDot.frame.origin.y - 2.0f - halfLabelHeight; - newPopUpLabel.center = CGPointMake(xCenter, yCenter); - - // Check for bumping into top OR overlap with left neighbors if this is not a touch-driven popup. Remember, the user can only touch one popup at time, so it doesn't matter if there's overlap with those. - CGRect leftNeighborFrame = (reuseNumber >= 1) ? self.permanentPopups[reuseNumber-1].frame : CGRectNull; - CGRect secondNeighborFrame = (reuseNumber >= 2) ? self.permanentPopups[reuseNumber-2].frame : CGRectNull; - if (CGRectGetMinY(newPopUpLabel.frame) < 2.0f || - (!CGRectIsNull(leftNeighborFrame) && !CGRectIsNull(CGRectIntersection(newPopUpLabel.frame, leftNeighborFrame))) || - (!CGRectIsNull(secondNeighborFrame) && !CGRectIsNull(CGRectIntersection(newPopUpLabel.frame, secondNeighborFrame)))) { - // If so, try below point instead - CGRect frame = newPopUpLabel.frame; - frame.origin.y = CGRectGetMaxY(circleDot.frame)+2.0f; - newPopUpLabel.frame = frame; - // Check for bottom and again for overlap with neighbor and even neighbor second to the left + popUpLabel.center = CGPointMake(xCenter, popUpLabel.center.y); +} + +-(BOOL) adjustYLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) dotFrame andNeighbors: (CGRect) leftNeightbor and: (CGRect) secondNeighbor { + //returns YES if it can avoid those neighbors + //note: nil.frame == CGRectZero + //check for bumping into top OR overlap with left neighbors + //default Y is above point + CGFloat halfLabelHeight = popUpLabel.frame.size.height/2.0f; + popUpLabel.center = CGPointMake(popUpLabel.center.x, CGRectGetMinY(dotFrame) - 12.0f - halfLabelHeight ); + if (CGRectGetMinY(popUpLabel.frame) < 2.0f || + (!CGRectIsEmpty(CGRectIntersection(popUpLabel.frame, leftNeightbor))) || + (!CGRectIsEmpty(CGRectIntersection(popUpLabel.frame, secondNeighbor)))) { + //if so, try below point instead + CGRect frame = popUpLabel.frame; + frame.origin.y = CGRectGetMaxY(dotFrame)+12.0f; + popUpLabel.frame = frame; + //check for bottom and again for overlap with neighbor and even neighbor second to the left if (CGRectGetMaxY(frame) > (self.frame.size.height - self.XAxisLabelYOffset) || - (!CGRectIsNull(leftNeighborFrame) && !CGRectIsNull(CGRectIntersection(newPopUpLabel.frame, leftNeighborFrame))) || - (!CGRectIsNull(secondNeighborFrame) && !CGRectIsNull(CGRectIntersection(newPopUpLabel.frame, secondNeighborFrame)))) { - return nil; + (!CGRectIsEmpty(CGRectIntersection(popUpLabel.frame, leftNeightbor))) || + (!CGRectIsEmpty(CGRectIntersection(popUpLabel.frame, secondNeighbor)))) { + return NO; } } - - if (self.animationGraphEntranceTime <= 0) { - newPopUpLabel.alpha = 1.0f; - } else { - [UIView animateWithDuration:0.5f delay:self.animationGraphEntranceTime options:UIViewAnimationOptionCurveLinear animations:^{ - newPopUpLabel.alpha = 1.0f; - } completion:nil]; - } - - return newPopUpLabel; + return YES; } + - (UIImage *)graphSnapshotImage { return [self graphSnapshotImageRenderedWhileInBackground:NO]; } @@ -1207,6 +1195,7 @@ - (UIImage *)graphSnapshotImageRenderedWhileInBackground:(BOOL)appIsInBackground - (void)reloadGraph { [self drawGraph]; + // [self setNeedsLayout]; } #pragma mark - Values @@ -1241,9 +1230,13 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { if (gestureRecognizer.numberOfTouches >= self.touchReportFingersRequired) { CGPoint translation = [self.panGesture velocityInView:self.panView]; return fabs(translation.y) < fabs(translation.x); - } else return NO; + } else { + return NO; + } return YES; - } else return [super gestureRecognizerShouldBegin:gestureRecognizer]; + } else { + return [super gestureRecognizerShouldBegin:gestureRecognizer]; + } } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { @@ -1264,55 +1257,82 @@ - (void)handleGestureAction:(UIGestureRecognizer *)recognizer { self.touchInputLine.alpha = self.alphaTouchInputLine; BEMCircle *closestDot = [self closestDotFromTouchInputLine:self.touchInputLine]; - closestDot.alpha = 0.8f; - - if (self.enablePopUpReport == YES && closestDot.tag >= DotFirstTag100 && [closestDot isKindOfClass:[BEMCircle class]] && self.alwaysDisplayPopUpLabels == NO) { - [self labelForPoint:closestDot reuseNumber:NSIntegerMax]; + NSUInteger index = 0; + if (closestDot.tag > DotFirstTag100) { + index = closestDot.tag - DotFirstTag100; + } else { + if (numberOfPoints == 0) return; //something's very wrong } + closestDot.alpha = 0.8f; - if (closestDot.tag >= DotFirstTag100 && [closestDot isMemberOfClass:[BEMCircle class]]) { - if ([self.delegate respondsToSelector:@selector(lineGraph:didTouchGraphWithClosestIndex:)] && self.enableTouchReport == YES) { - [self.delegate lineGraph:self didTouchGraphWithClosestIndex:((NSUInteger)closestDot.tag - DotFirstTag100)]; - + if (recognizer.state != UIGestureRecognizerStateEnded) { + //ON START OR MOVE + if (self.enablePopUpReport == YES && self.alwaysDisplayPopUpLabels == NO) { + if (!self.customPopUpView && [self.delegate respondsToSelector:@selector(popUpViewForLineGraph:)] ) { + self.customPopUpView = [self.delegate popUpViewForLineGraph:self]; + } + if (self.customPopUpView) { + [self addSubview:self.customPopUpView]; + [self adjustXLocForLabel:self.customPopUpView avoidingDot:closestDot.frame]; + [self adjustYLocForLabel:self.customPopUpView avoidingDot:closestDot.frame andNeighbors:CGRectZero and:CGRectZero]; + if ([self.delegate respondsToSelector:@selector(lineGraph:modifyPopupView:forIndex:)]) { + self.customPopUpView.alpha = 1.0f; + [self.delegate lineGraph:self modifyPopupView:self.customPopUpView forIndex:index]; + } else { + [UIView animateWithDuration:0.2f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.customPopUpView.alpha = 1.0f; + } completion:nil]; + } + } else { + if (!self.popUpLabel) { + self.popUpLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + } + [self addSubview: self.popUpLabel ]; + self.popUpLabel = [self configureLabel:self.popUpLabel forPoint:closestDot]; + [self adjustXLocForLabel:self.popUpLabel avoidingDot:closestDot.frame]; + [self adjustYLocForLabel:self.popUpLabel avoidingDot:closestDot.frame andNeighbors:CGRectZero and:CGRectZero]; + [UIView animateWithDuration:0.2f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.popUpLabel.alpha = 1.0f; + } completion:nil]; + } } - } - - // ON RELEASE - if (recognizer.state == UIGestureRecognizerStateEnded) { - if ([self.delegate respondsToSelector:@selector(lineGraph:didReleaseTouchFromGraphWithClosestIndex:)]) { - [self.delegate lineGraph:self didReleaseTouchFromGraphWithClosestIndex:(closestDot.tag - DotFirstTag100)]; + if (self.enableTouchReport && [self.delegate respondsToSelector:@selector(lineGraph:didTouchGraphWithClosestIndex:)]) { + [self.delegate lineGraph:self didTouchGraphWithClosestIndex:index]; + } + } else { + // ON RELEASE + if (self.enableTouchReport && [self.delegate respondsToSelector:@selector(lineGraph:didReleaseTouchFromGraphWithClosestIndex:)]) { + [self.delegate lineGraph:self didReleaseTouchFromGraphWithClosestIndex:index]; } [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ if (self.alwaysDisplayDots == NO && self.displayDotsOnly == NO) { closestDot.alpha = 0; } - self.touchInputLine.alpha = 0; - if (self.enablePopUpReport == YES) { - self.popUpLabel.alpha = 0; - if (self.popUpView) self.popUpView.alpha = 0; - } - } completion:nil]; + self.popUpLabel.alpha = 0; + self.customPopUpView.alpha = 0; + } completion:^(BOOL finished) { + [self.customPopUpView removeFromSuperview]; + self.customPopUpView = nil; + }]; } } #pragma mark - Graph Calculations - (BEMCircle *)closestDotFromTouchInputLine:(UIView *)touchInputLine { - BEMCircle *closestDot = nil; + BEMCircle * closestDot = nil; CGFloat currentlyCloser = CGFLOAT_MAX; for (BEMCircle *point in self.circleDots) { - if (point.tag >= DotFirstTag100) { - if (self.alwaysDisplayDots == NO && self.displayDotsOnly == NO) { - point.alpha = 0; - } - CGFloat distance = (CGFloat)fabs(point.center.x - touchInputLine.center.x) ; - if (distance < currentlyCloser) { - currentlyCloser = distance; - closestDot = point; - } + if (self.alwaysDisplayDots == NO && self.displayDotsOnly == NO) { + point.alpha = 0; + } + CGFloat distance = (CGFloat)fabs(point.center.x - touchInputLine.center.x) ; + if (distance < currentlyCloser) { + currentlyCloser = distance; + closestDot = point; } } return closestDot; @@ -1322,15 +1342,11 @@ - (CGFloat)getMaximumValue { if ([self.delegate respondsToSelector:@selector(maxValueForLineGraph:)]) { return [self.delegate maxValueForLineGraph:self]; } else { - CGFloat dotValue; CGFloat maxValue = -FLT_MAX; @autoreleasepool { for (NSUInteger i = 0; i < numberOfPoints; i++) { - if ([self.dataSource respondsToSelector:@selector(lineGraph:valueForPointAtIndex:)]) { - dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:i]; - } else dotValue = 0; - + CGFloat dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:i]; if (dotValue >= BEMNullGraphValue) continue; if (dotValue > maxValue) maxValue = dotValue; } @@ -1343,16 +1359,11 @@ - (CGFloat)getMinimumValue { if ([self.delegate respondsToSelector:@selector(minValueForLineGraph:)]) { return [self.delegate minValueForLineGraph:self]; } else { - CGFloat dotValue; CGFloat minValue = INFINITY; @autoreleasepool { for (NSUInteger i = 0; i < numberOfPoints; i++) { - if ([self.dataSource respondsToSelector:@selector(lineGraph:valueForPointAtIndex:)]) { - dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:i]; - - } else dotValue = 0; - + CGFloat dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:i]; if (dotValue >= BEMNullGraphValue) continue; if (dotValue < minValue) minValue = dotValue; } @@ -1360,9 +1371,30 @@ - (CGFloat)getMinimumValue { return minValue; } } +- (CGFloat)getAverageValue { + if ([self.delegate respondsToSelector:@selector(averageValueForLineGraph:)]) { + return [self.delegate averageValueForLineGraph:self]; + } else { + CGFloat sumValue = 0.0f; + int numPoints = 0; + @autoreleasepool { + for (NSUInteger i = 0; i < numberOfPoints; i++) { + CGFloat dotValue = [self.dataSource lineGraph:self valueForPointAtIndex:i]; + if (dotValue >= BEMNullGraphValue) continue; + sumValue += dotValue; + numPoints++; + } + } + if (numPoints > 0) { + return sumValue/numPoints; + } else { + return NAN; + } + } +} - (CGFloat)yPositionForDotValue:(CGFloat)dotValue { - if (dotValue >= BEMNullGraphValue) { + if (isnan(dotValue) || dotValue >= BEMNullGraphValue) { return BEMNullGraphValue; } @@ -1394,7 +1426,8 @@ - (CGFloat)yPositionForDotValue:(CGFloat)dotValue { #pragma mark - Deprecated Methods -- (NSNumber *)calculatePointValueSum { + + - (NSNumber *)calculatePointValueSum { [self printDeprecationTransitionWarningForOldMethod:@"calculatePointValueSum" replacementMethod:@"calculatePointValueSumOnGraph:" newObject:@"BEMGraphCalculator" sharedInstance:YES]; return [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:self]; } From 9bfe179dc8d097d94787827ff00b770f29ee8032 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sat, 18 Mar 2017 16:19:54 -0700 Subject: [PATCH 02/15] Add Types to all Arrays --- Classes/BEMAverageLine.h | 2 +- Classes/BEMGraphCalculator.m | 42 +++++++++---------- Classes/BEMLine.h | 12 +++--- Classes/BEMLine.m | 24 +++++------ Classes/BEMSimpleLineGraphView.h | 6 +-- Classes/BEMSimpleLineGraphView.m | 6 +-- .../SimpleLineChart/ViewController.h | 6 +-- .../SimpleLineChartTests/CustomizationTests.m | 28 ++++++------- .../SimpleLineChartTests.m | 40 +++++++++--------- 9 files changed, 82 insertions(+), 84 deletions(-) diff --git a/Classes/BEMAverageLine.h b/Classes/BEMAverageLine.h index 3d2b07d..1e1f8f8 100644 --- a/Classes/BEMAverageLine.h +++ b/Classes/BEMAverageLine.h @@ -35,7 +35,7 @@ /// Dash pattern for the average line -@property (strong, nonatomic, nullable) NSArray *dashPattern; +@property (strong, nonatomic, nullable) NSArray *dashPattern; //Label for average line in y axis. Default is blank. diff --git a/Classes/BEMGraphCalculator.m b/Classes/BEMGraphCalculator.m index 0cae401..8c2604e 100644 --- a/Classes/BEMGraphCalculator.m +++ b/Classes/BEMGraphCalculator.m @@ -41,13 +41,13 @@ - (instancetype)init { // MARK: - // MARK: Essential Calculations -- (nonnull NSArray *)calculationDataPointsOnGraph:(nonnull BEMSimpleLineGraphView *)graph { +- (nonnull NSArray *)calculationDataPointsOnGraph:(nonnull BEMSimpleLineGraphView *)graph { NSPredicate *filter = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) { NSNumber *value = (NSNumber *)evaluatedObject; BOOL retVal = ![value isEqualToNumber:@(BEMNullGraphValue)]; return retVal; }]; - NSArray *filteredArray = [[graph graphValuesForDataPoints] filteredArrayUsingPredicate:filter]; + NSArray *filteredArray = [[graph graphValuesForDataPoints] filteredArrayUsingPredicate:filter]; return filteredArray; } @@ -55,7 +55,7 @@ - (nonnull NSArray *)calculationDataPointsOnGraph:(nonnull BEMSimpleLineGraphVie // MARK: Basic Statistics - (nonnull NSNumber *)calculatePointValueAverageOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"average:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -65,7 +65,7 @@ - (nonnull NSNumber *)calculatePointValueAverageOnGraph:(nonnull BEMSimpleLineGr } - (nonnull NSNumber *)calculatePointValueSumOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"sum:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -75,7 +75,7 @@ - (nonnull NSNumber *)calculatePointValueSumOnGraph:(nonnull BEMSimpleLineGraphV } - (nonnull NSNumber *)calculatePointValueMedianOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"median:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -85,19 +85,19 @@ - (nonnull NSNumber *)calculatePointValueMedianOnGraph:(nonnull BEMSimpleLineGra } - (nonnull NSNumber *)calculatePointValueModeOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"mode:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; - NSMutableArray *value = [expression expressionValueWithObject:nil context:nil]; - NSNumber *numberValue = [value firstObject]; + NSMutableArray *values = [expression expressionValueWithObject:nil context:nil]; + NSNumber *numberValue = [values firstObject]; if (numberValue) return numberValue; else return [NSNumber numberWithInt:0]; } - (nonnull NSNumber *)calculateStandardDeviationOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"stddev:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -110,7 +110,7 @@ - (nonnull NSNumber *)calculateStandardDeviationOnGraph:(nonnull BEMSimpleLineGr // MARK: Minimum / Maximum - (nonnull NSNumber *)calculateMinimumPointValueOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"min:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -119,7 +119,7 @@ - (nonnull NSNumber *)calculateMinimumPointValueOnGraph:(nonnull BEMSimpleLineGr } - (nonnull NSNumber *)calculateMaximumPointValueOnGraph:(nonnull BEMSimpleLineGraphView *)graph { - NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; + NSArray *filteredArray = [self calculationDataPointsOnGraph:graph]; if (filteredArray.count == 0) return [NSNumber numberWithInt:0]; NSExpression *expression = [NSExpression expressionForFunction:@"max:" arguments:@[[NSExpression expressionForConstantValue:filteredArray]]]; @@ -132,7 +132,7 @@ - (nonnull NSNumber *)calculateMaximumPointValueOnGraph:(nonnull BEMSimpleLineGr // MARK: Integration - (nonnull NSNumber *)calculateAreaUsingIntegrationMethod:(BEMIntegrationMethod)integrationMethod onGraph:(nonnull BEMSimpleLineGraphView *)graph xAxisScale:(nonnull NSNumber *)scale { - NSArray *fixedDataPoints = [self calculationDataPointsOnGraph:graph]; + NSArray *fixedDataPoints = [self calculationDataPointsOnGraph:graph]; if (integrationMethod == BEMIntegrationMethodLeftReimannSum) return [self integrateUsingLeftReimannSum:fixedDataPoints xAxisScale:scale]; else if (integrationMethod == BEMIntegrationMethodRightReimannSum) return [self integrateUsingRightReimannSum:fixedDataPoints xAxisScale:scale]; else if (integrationMethod == BEMIntegrationMethodTrapezoidalSum) return [self integrateUsingTrapezoidalSum:fixedDataPoints xAxisScale:scale]; @@ -140,10 +140,10 @@ - (nonnull NSNumber *)calculateAreaUsingIntegrationMethod:(BEMIntegrationMethod) else return [NSNumber numberWithInt:0]; } -- (NSNumber *)integrateUsingLeftReimannSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { +- (NSNumber *)integrateUsingLeftReimannSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { NSNumber *totalArea = [NSNumber numberWithInt:0]; - NSMutableArray *leftSumPoints = graphPoints.mutableCopy; + NSMutableArray *leftSumPoints = graphPoints.mutableCopy; [leftSumPoints removeLastObject]; for (NSNumber *yValue in leftSumPoints) { @@ -154,10 +154,10 @@ - (NSNumber *)integrateUsingLeftReimannSum:(nonnull NSArray *)graphPoints xAxisS return totalArea; } -- (NSNumber *)integrateUsingRightReimannSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { +- (NSNumber *)integrateUsingRightReimannSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { NSNumber *totalArea = [NSNumber numberWithInt:0]; - NSMutableArray *rightSumPoints = graphPoints.mutableCopy; + NSMutableArray *rightSumPoints = graphPoints.mutableCopy; [rightSumPoints removeObjectAtIndex:0]; for (NSNumber *yValue in rightSumPoints) { @@ -168,16 +168,16 @@ - (NSNumber *)integrateUsingRightReimannSum:(nonnull NSArray *)graphPoints xAxis return totalArea; } -- (NSNumber *)integrateUsingTrapezoidalSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { +- (NSNumber *)integrateUsingTrapezoidalSum:(nonnull NSArray *)graphPoints xAxisScale:(nonnull NSNumber *)scale { NSNumber *left = [self integrateUsingLeftReimannSum:graphPoints xAxisScale:scale]; NSNumber *right = [self integrateUsingRightReimannSum:graphPoints xAxisScale:scale]; NSNumber *trapezoidal = [NSNumber numberWithFloat:(left.floatValue+right.floatValue)/2]; return trapezoidal; } -- (NSNumber *)integrateUsingParabolicSimpsonSum:(nonnull NSArray *)points xAxisScale:(nonnull NSNumber *)scale { +- (NSNumber *)integrateUsingParabolicSimpsonSum:(nonnull NSArray *)points xAxisScale:(nonnull NSNumber *)scale { // Get all the points from the graph into a mutable array - NSMutableArray *graphPoints = points.mutableCopy; + NSMutableArray *graphPoints = points.mutableCopy; // If there are two or fewer points on the graph, no parabolic curve can be created. Thus, the next most accurate method will be employed: a trapezoidal summation if (graphPoints.count <= 2) return [self integrateUsingTrapezoidalSum:points xAxisScale:scale]; @@ -233,8 +233,8 @@ - (NSNumber *)integrateUsingParabolicSimpsonSum:(nonnull NSArray *)points xAxisS - (NSNumber *)calculateCorrelationCoefficientUsingCorrelationMethod:(BEMCorrelationMethod)correlationMethod onGraph:(BEMSimpleLineGraphView *)graph xAxisScale:(nonnull NSNumber *)scale { // Grab the x and y points // Because a BEMSimpleLineGraph object simply increments X-Values, we must calculate the values here - NSArray *yPoints = [self calculationDataPointsOnGraph:graph]; - NSMutableArray *xPoints = [NSMutableArray arrayWithCapacity:yPoints.count]; + NSArray *yPoints = [self calculationDataPointsOnGraph:graph]; + NSMutableArray *xPoints = [NSMutableArray arrayWithCapacity:yPoints.count]; if (scale == nil || scale.floatValue == 0.0) { for (NSUInteger i = 1; i <= yPoints.count; i++) { [xPoints addObject:[NSNumber numberWithInteger:i]]; diff --git a/Classes/BEMLine.h b/Classes/BEMLine.h index 3572cfc..82a7fa1 100644 --- a/Classes/BEMLine.h +++ b/Classes/BEMLine.h @@ -43,19 +43,19 @@ typedef NS_ENUM(NSUInteger, BEMLineGradientDirection) { //----- POINTS -----// /// All of the Y-axis values for the points -@property (strong, nonatomic, nonnull) NSArray *arrayOfPoints; +@property (strong, nonatomic, nonnull) NSArray *arrayOfPoints; /// All of the X-Axis coordinates used to draw vertical lines through -@property (strong, nonatomic, nonnull) NSArray *arrayOfVerticalReferenceLinePoints; +@property (strong, nonatomic, nonnull) NSArray *arrayOfVerticalReferenceLinePoints; /// The value used to offset the fringe vertical reference lines when the x-axis labels are on the edge @property (assign, nonatomic) CGFloat verticalReferenceHorizontalFringeNegation; /// All of the Y-Axis coordinates used to draw horizontal lines through -@property (strong, nonatomic, nullable) NSArray *arrayOfHorizontalReferenceLinePoints; +@property (strong, nonatomic, nullable) NSArray *arrayOfHorizontalReferenceLinePoints; /// All of the point values -@property (strong, nonatomic, nullable) NSArray *arrayOfValues; +@property (strong, nonatomic, nullable) NSArray *arrayOfValues; /** Draw thin, translucent, reference lines using the provided X-Axis and Y-Axis coordinates. @see Use \p arrayOfVerticalReferenceLinePoints to specify vertical reference lines' positions. Use \p arrayOfHorizontalReferenceLinePoints to specify horizontal reference lines' positions. */ @@ -77,10 +77,10 @@ typedef NS_ENUM(NSUInteger, BEMLineGradientDirection) { @property (assign, nonatomic) BOOL enableTopReferenceFrameLine; /** Dash pattern for the references line on the X axis */ -@property (nonatomic, strong, nullable) NSArray *lineDashPatternForReferenceXAxisLines; +@property (nonatomic, strong, nullable) NSArray *lineDashPatternForReferenceXAxisLines; /** Dash pattern for the references line on the Y axis */ -@property (nonatomic, strong, nullable) NSArray *lineDashPatternForReferenceYAxisLines; +@property (nonatomic, strong, nullable) NSArray *lineDashPatternForReferenceYAxisLines; /** If a null value is present, interpolation would draw a best fit line through the null point bound by its surrounding points. Default: YES */ @property (assign, nonatomic) BOOL interpolateNullValues; diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index fd8800e..2ebac1f 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -19,7 +19,7 @@ @interface BEMLine() -@property (nonatomic, strong) NSMutableArray *points; +@property (nonatomic, strong) NSMutableArray *points; @end @@ -286,39 +286,38 @@ - (void)drawRect:(CGRect)rect { } } -- (NSArray *)topPointsArray { +- (NSArray *)topPointsArray { CGPoint topPointZero = CGPointMake(0,0); CGPoint topPointFull = CGPointMake(self.frame.size.width, 0); - NSMutableArray *topPoints = [NSMutableArray arrayWithArray:self.points]; + NSMutableArray *topPoints = [NSMutableArray arrayWithArray:self.points]; [topPoints insertObject:[NSValue valueWithCGPoint:topPointZero] atIndex:0]; [topPoints addObject:[NSValue valueWithCGPoint:topPointFull]]; return topPoints; } -- (NSArray *)bottomPointsArray { +- (NSArray *)bottomPointsArray { CGPoint bottomPointZero = CGPointMake(0, self.frame.size.height); CGPoint bottomPointFull = CGPointMake(self.frame.size.width, self.frame.size.height); - NSMutableArray *bottomPoints = [NSMutableArray arrayWithArray:self.points]; + NSMutableArray *bottomPoints = [NSMutableArray arrayWithArray:self.points]; [bottomPoints insertObject:[NSValue valueWithCGPoint:bottomPointZero] atIndex:0]; [bottomPoints addObject:[NSValue valueWithCGPoint:bottomPointFull]]; return bottomPoints; } -+ (UIBezierPath *)linesToPoints:(NSArray *)points { ++ (UIBezierPath *)linesToPoints:(NSArray *)points { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; [path moveToPoint:p1]; - for (NSUInteger i = 1; i < points.count; i++) { - value = points[i]; - CGPoint p2 = [value CGPointValue]; + for (NSValue * point in points) { + CGPoint p2 = [point CGPointValue]; [path addLineToPoint:p2]; } return path; } -+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { ++ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; @@ -332,9 +331,8 @@ + (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { return path; } - for (NSUInteger i = 1; i < points.count; i++) { - value = points[i]; - CGPoint p2 = [value CGPointValue]; + for (NSValue * point in points) { + CGPoint p2 = [point CGPointValue]; CGPoint midPoint = midPointForPoints(p1, p2); [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; diff --git a/Classes/BEMSimpleLineGraphView.h b/Classes/BEMSimpleLineGraphView.h index fd29532..3b1de6b 100644 --- a/Classes/BEMSimpleLineGraphView.h +++ b/Classes/BEMSimpleLineGraphView.h @@ -336,11 +336,11 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView *lineDashPatternForReferenceXAxisLines; /// A line dash patter to be applied to Y axis reference lines. This allows you to draw a dotted or hashed line -@property (nonatomic, strong) NSArray *lineDashPatternForReferenceYAxisLines; +@property (nonatomic, strong) NSArray *lineDashPatternForReferenceYAxisLines; /// Color to be used for the no data label on the chart @@ -568,7 +568,7 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView *)incrementPositionsForXAxisOnLineGraph:(BEMSimpleLineGraphView *)graph; diff --git a/Classes/BEMSimpleLineGraphView.m b/Classes/BEMSimpleLineGraphView.m index 38602f5..3d4586d 100644 --- a/Classes/BEMSimpleLineGraphView.m +++ b/Classes/BEMSimpleLineGraphView.m @@ -784,7 +784,7 @@ - (void)drawXAxis { __block UILabel *prevLabel; - NSMutableArray *overlapLabels = [NSMutableArray arrayWithCapacity:self.xAxisLabels.count]; + NSMutableArray *overlapLabels = [NSMutableArray arrayWithCapacity:self.xAxisLabels.count]; [self.xAxisLabels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger idx, BOOL *stop) { if (idx == 0) { prevLabel = label; //always show first label @@ -987,7 +987,7 @@ - (void)drawYAxis { value = increment/2; } } - NSMutableArray *dotValues = [[NSMutableArray alloc] initWithCapacity:numberOfLabels]; + NSMutableArray *dotValues = [[NSMutableArray alloc] initWithCapacity:numberOfLabels]; for (NSUInteger i = 0; i < numberOfLabels; i++) { [dotValues addObject:@(value)]; value += increment; @@ -1013,7 +1013,7 @@ - (void)drawYAxis { // Detect overlapped labels __block UILabel * prevLabel = nil;; - NSMutableArray *overlapLabels = [NSMutableArray arrayWithCapacity:0]; + NSMutableArray *overlapLabels = [NSMutableArray arrayWithCapacity:0]; [self.yAxisLabels enumerateObjectsUsingBlock:^(UILabel *label, NSUInteger idx, BOOL *stop) { diff --git a/Sample Project/SimpleLineChart/ViewController.h b/Sample Project/SimpleLineChart/ViewController.h index d2d44cf..e41afd2 100644 --- a/Sample Project/SimpleLineChart/ViewController.h +++ b/Sample Project/SimpleLineChart/ViewController.h @@ -14,8 +14,8 @@ @property (weak, nonatomic) IBOutlet BEMSimpleLineGraphView *myGraph; -@property (strong, nonatomic) NSMutableArray *arrayOfValues; -@property (strong, nonatomic) NSMutableArray *arrayOfDates; +@property (strong, nonatomic) NSMutableArray *arrayOfValues; +@property (strong, nonatomic) NSMutableArray *arrayOfDates; @property (strong, nonatomic) IBOutlet UILabel *labelValues; @property (strong, nonatomic) IBOutlet UILabel *labelDates; @@ -29,4 +29,4 @@ - (IBAction)displayStatistics:(id)sender; -@end \ No newline at end of file +@end diff --git a/Sample Project/SimpleLineChartTests/CustomizationTests.m b/Sample Project/SimpleLineChartTests/CustomizationTests.m index 4aad044..809a843 100644 --- a/Sample Project/SimpleLineChartTests/CustomizationTests.m +++ b/Sample Project/SimpleLineChartTests/CustomizationTests.m @@ -12,7 +12,7 @@ #pragma clang diagnostic ignored "-Wfloat-equal" @interface BEMSimpleLineGraphView () -// Allow tester to get to internal properties +//Allow tester to get to internal properties /// All of the dataPoint labels @property (strong, nonatomic) NSMutableArray *permanentPopups; @@ -32,7 +32,7 @@ @implementation CustomizationTests - (void)setUp { [super setUp]; - + self.lineGraph = [[BEMSimpleLineGraphView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; self.lineGraph.delegate = self; self.lineGraph.dataSource = self; @@ -64,14 +64,14 @@ - (NSString *)popUpSuffixForlineGraph:(BEMSimpleLineGraphView * __nonnull)graph - (void)testDotCustomization { CGFloat sizePoint = 20.0; - + self.lineGraph.alwaysDisplayDots = YES; self.lineGraph.animationGraphEntranceTime = 0.0; self.lineGraph.sizePoint = sizePoint; self.lineGraph.colorPoint = [UIColor greenColor]; [self.lineGraph reloadGraph]; - - NSArray *dots = self.lineGraph.circleDots; + + NSArray *dots = self.lineGraph.circleDots; XCTAssert(dots.count == numberOfPoints, @"There should be as many BEMCircle views in the graph's subviews as the data source method 'numberOfPointsInLineGraph:' returns"); @@ -90,10 +90,10 @@ - (void)testXAxisCustomization { self.lineGraph.labelFont = font; self.lineGraph.colorXaxisLabel = [UIColor greenColor]; [self.lineGraph reloadGraph]; - - NSArray *labels = [self.lineGraph graphLabelsForXAxis]; + + NSArray *labels = [self.lineGraph graphLabelsForXAxis]; XCTAssert(labels.count == numberOfPoints, @"The number of X-Axis labels should be the same as the number of points on the graph"); - + for (UILabel *XAxisLabel in labels) { XCTAssert([XAxisLabel isMemberOfClass:[UILabel class]], @"The array returned by 'graphLabelsForXAxis' should only return UILabels"); XCTAssert([XAxisLabel.text isEqualToString:xAxisLabelString], @"The X-Axis label's strings should be the same as the one returned by the data source method 'labelOnXAxisForIndex:'"); @@ -110,17 +110,17 @@ - (void)testPopUps { UIFont *font = [UIFont systemFontOfSize:25.0]; self.lineGraph.labelFont = font; [self.lineGraph reloadGraph]; - - NSMutableArray *popUps = self.lineGraph.permanentPopups; - + + NSMutableArray *popUps = self.lineGraph.permanentPopups; + XCTAssert(popUps.count == numberOfPoints, @"We should have a popup above each and every dot"); - NSString *expectedLabelText = [NSString stringWithFormat:@"%@%.f%@", popUpPrefix,pointValue,popUpSuffix]; + NSString *expectedLabelText = [NSString stringWithFormat:@"%@%.f%@", popUpPrefix,pointValue,popUpSuffix]; for (UILabel *popUp in popUps) { XCTAssert([popUp isMemberOfClass:[UILabel class]],@"Popups must be label class"); // XCTAssert(popUp.backgroundColor == [UIColor greenColor],@"") // XCTAssert(popUp.layer.alpha >= 0.69 && popUp.alpha <= 0.71, @"The popups should always be displayed and have an alpha of 0.7"); - UIColor * expectedColor = [UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:0.7f]; - XCTAssert(CGColorEqualToColor(popUp.layer.backgroundColor, expectedColor.CGColor), @"The popups background color should be the one set by the property"); + UIColor * expectedColor = [UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:0.7f]; + XCTAssert(CGColorEqualToColor(popUp.layer.backgroundColor, expectedColor.CGColor), @"The popups background color should be the one set by the property"); XCTAssert([popUp.text isEqualToString:expectedLabelText], @"The popup labels should display the value of the dot and the suffix and prefix returned by the delegate"); XCTAssert(popUp.font == font, @"The popup label's font is expected to be the customized one"); XCTAssert(popUp.backgroundColor == [UIColor clearColor], @"The popup label's background color should always be clear color"); diff --git a/Sample Project/SimpleLineChartTests/SimpleLineChartTests.m b/Sample Project/SimpleLineChartTests/SimpleLineChartTests.m index c86e757..4911328 100644 --- a/Sample Project/SimpleLineChartTests/SimpleLineChartTests.m +++ b/Sample Project/SimpleLineChartTests/SimpleLineChartTests.m @@ -33,7 +33,7 @@ @implementation SimpleLineGraphTests - (void)setUp { [super setUp]; - + self.lineGraph = [[BEMSimpleLineGraphView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)]; self.lineGraph.delegate = self; self.lineGraph.dataSource = self; @@ -71,10 +71,10 @@ - (void)testReloadDataPerformance { - (void)testGraphValuesForXAxis { [self.lineGraph reloadGraph]; - - NSArray *xAxisStrings = [self.lineGraph graphValuesForXAxis]; + + NSArray *xAxisStrings = [self.lineGraph graphValuesForXAxis]; XCTAssert(xAxisStrings.count == numberOfPoints, @"The number of strings on the X-Axis should be equal to the number returned by the data source method 'numberOfPointsInLineGraph:'"); - + for (NSString *xAxisString in xAxisStrings) { XCTAssert([xAxisString isKindOfClass:[NSString class]], @"The array returned by 'graphValuesForXAxis' should only return NSStrings"); XCTAssert([xAxisString isEqualToString:xAxisLabelString], @"The X-Axis strings should be the same as the one returned by the data source method 'labelOnXAxisForIndex:'"); @@ -83,11 +83,11 @@ - (void)testGraphValuesForXAxis { - (void)testGraphValuesForDataPoints { [self.lineGraph reloadGraph]; - - NSArray *values = [self.lineGraph graphValuesForDataPoints]; + + NSArray *values = [self.lineGraph graphValuesForDataPoints]; XCTAssert(values.count == numberOfPoints, @"The number of data points should be equal to the number returned by the data source method 'numberOfPointsInLineGraph:'"); - - NSMutableArray *mockedValues = [NSMutableArray new]; + + NSMutableArray *mockedValues = [NSMutableArray new]; for (NSUInteger i = 0; i < numberOfPoints; i++) { [mockedValues addObject:[NSNumber numberWithFloat:pointValue]]; } @@ -97,15 +97,15 @@ - (void)testGraphValuesForDataPoints { - (void)testDrawnPoints { self.lineGraph.animationGraphEntranceTime = 0.0; [self.lineGraph reloadGraph]; - - NSMutableArray *dots = self.lineGraph.circleDots; + + NSMutableArray *dots = self.lineGraph.circleDots; XCTAssert(dots.count == numberOfPoints, @"There should be as many BEMCircle views in the graph's subviews as the data source method 'numberOfPointsInLineGraph:' returns"); - + for (BEMCircle *dot in dots) { XCTAssert(dot.bounds.size.width == 10.0, @"Dots are expected to have a default width of 10.0"); XCTAssert(dot.bounds.size.height == 10.0, @"Dots are expected to have a default height of 10.0"); - XCTAssert([dot.color isEqual:[UIColor colorWithWhite:1.0f alpha:0.7f]], @"Dots are expected to be white at alpha 0.7 by default"); + XCTAssert([dot.color isEqual:[UIColor colorWithWhite:1.0 alpha:0.7]], @"Dots are expected to be white at alpha 0.7 by default"); XCTAssert(dot.absoluteValue == pointValue, @"Dots are expected to have a value equal to the value returned by the data source method 'valueForPointAtIndex:'"); XCTAssert(dot.alpha == 0.0, @"Dots are expected to not be displayed by default (alpha of 0)"); XCTAssert([dot.backgroundColor isEqual:[UIColor clearColor]], @"Dots are expected to have a clearColor background color by default"); @@ -115,15 +115,15 @@ - (void)testDrawnPoints { - (void)testGraphLabelsForXAxis { self.lineGraph.enableXAxisLabel = NO; [self.lineGraph reloadGraph]; - + XCTAssert([self.lineGraph graphLabelsForXAxis].count == 0, @"Should be no labels on XAxis"); - + self.lineGraph.enableXAxisLabel = YES; [self.lineGraph reloadGraph]; - - NSArray *labels = [self.lineGraph graphLabelsForXAxis]; + + NSArray *labels = [self.lineGraph graphLabelsForXAxis]; XCTAssert(labels.count == numberOfPoints, @"The number of X-Axis labels should be the same as the number of points on the graph"); - + for (UILabel *XAxisLabel in labels) { XCTAssert([XAxisLabel isMemberOfClass:[UILabel class]], @"The array returned by 'graphLabelsForXAxis' should only return UILabels"); XCTAssert([XAxisLabel.text isEqualToString:xAxisLabelString], @"The X-Axis label's strings should be the same as the one returned by the data source method 'labelOnXAxisForIndex:'"); @@ -142,9 +142,9 @@ - (void)testYAxisLabels { self.lineGraph.enableYAxisLabel = YES; [self.lineGraph reloadGraph]; - + NSString *value = [NSString stringWithFormat:@"%.f", pointValue]; - NSMutableArray * yAxisLabels = [NSMutableArray array]; + NSMutableArray * yAxisLabels = [NSMutableArray array]; for (UILabel *label in [self.lineGraph graphLabelsForYAxis]) { if (label.superview) { [yAxisLabels addObject:label]; @@ -153,7 +153,7 @@ - (void)testYAxisLabels { XCTAssert([label.textColor isEqual:[UIColor blackColor]], @"The Y-Axis label is expected to have a text color of black by default"); XCTAssert([label.backgroundColor isEqual:[UIColor clearColor]], @"The Y-Axis label is expected to have a background color of clear by default"); } - + XCTAssert(yAxisLabels.count == 1, @"With all the dots having the same value, we only expect one Y axis label"); } From 78237352430f205fc76ad62b9749bb0452b46c74 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sun, 19 Mar 2017 10:49:38 -0700 Subject: [PATCH 03/15] Create new TestBed app to manipulate almost all parameters Add coding to AverageLine Move gradient properties to strong to properly retain Fix compiler error about integers used in enums Change alwaysDisplayPopupatIndex to use NSUInteger for index instead of float Fix Frame warning in SimpleLineChart's launch Screen --- Classes/BEMAverageLine.h | 2 +- Classes/BEMAverageLine.m | 39 + Classes/BEMLine.h | 6 +- Classes/BEMLine.m | 22 +- Classes/BEMSimpleLineGraphView.h | 8 +- Classes/BEMSimpleLineGraphView.m | 1 - .../SimpleLineChart.xcodeproj/project.pbxproj | 1299 ++++++---- .../Base.lproj/Launch Screen.storyboard | 19 +- .../SimpleLineChart/ViewController.m | 7 +- .../TestBed/ARFontPickerViewController.h | 46 + .../TestBed/ARFontPickerViewController.m | 98 + Sample Project/TestBed/AppDelegate.h | 17 + Sample Project/TestBed/AppDelegate.m | 59 + Sample Project/TestBed/AppIcon60x60@2x.png | Bin 0 -> 17651 bytes .../AppIcon.appiconset/AppIcon76x76@2x.png | Bin 0 -> 40011 bytes .../AppIcon83.5x83.5@2x.png | Bin 0 -> 43497 bytes .../AppIcon.appiconset/Contents.json | 55 + .../Base.lproj/LaunchScreen.storyboard | 45 + .../TestBed/Base.lproj/Main.storyboard | 2307 +++++++++++++++++ Sample Project/TestBed/DetailViewController.h | 43 + Sample Project/TestBed/DetailViewController.m | 358 +++ Sample Project/TestBed/Info.plist | 39 + Sample Project/TestBed/MasterViewController.h | 30 + Sample Project/TestBed/MasterViewController.m | 1014 ++++++++ Sample Project/TestBed/StatsViewController.h | 29 + Sample Project/TestBed/StatsViewController.m | 86 + Sample Project/TestBed/main.m | 16 + 27 files changed, 5064 insertions(+), 581 deletions(-) create mode 100644 Sample Project/TestBed/ARFontPickerViewController.h create mode 100644 Sample Project/TestBed/ARFontPickerViewController.m create mode 100644 Sample Project/TestBed/AppDelegate.h create mode 100644 Sample Project/TestBed/AppDelegate.m create mode 100644 Sample Project/TestBed/AppIcon60x60@2x.png create mode 100644 Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x.png create mode 100644 Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x.png create mode 100644 Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 Sample Project/TestBed/Base.lproj/LaunchScreen.storyboard create mode 100644 Sample Project/TestBed/Base.lproj/Main.storyboard create mode 100644 Sample Project/TestBed/DetailViewController.h create mode 100644 Sample Project/TestBed/DetailViewController.m create mode 100644 Sample Project/TestBed/Info.plist create mode 100644 Sample Project/TestBed/MasterViewController.h create mode 100644 Sample Project/TestBed/MasterViewController.m create mode 100644 Sample Project/TestBed/StatsViewController.h create mode 100644 Sample Project/TestBed/StatsViewController.m create mode 100644 Sample Project/TestBed/main.m diff --git a/Classes/BEMAverageLine.h b/Classes/BEMAverageLine.h index 1e1f8f8..528add8 100644 --- a/Classes/BEMAverageLine.h +++ b/Classes/BEMAverageLine.h @@ -11,7 +11,7 @@ /// A line displayed horizontally across the graph at the average y-value -@interface BEMAverageLine : NSObject +@interface BEMAverageLine : NSObject /// When set to YES, an average line will be displayed on the line graph diff --git a/Classes/BEMAverageLine.m b/Classes/BEMAverageLine.m index d59d91c..a4cbfa2 100644 --- a/Classes/BEMAverageLine.m +++ b/Classes/BEMAverageLine.m @@ -22,6 +22,45 @@ - (instancetype)init { return self; } + +- (instancetype) initWithCoder:(NSCoder *)coder { + +#define RestoreProperty(property, type) {\ +if ([coder containsValueForKey:@#property]) { \ +self.property = [coder decode ## type ##ForKey:@#property ]; \ +}\ +} + self = [self init]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" + + RestoreProperty (enableAverageLine, Bool); + RestoreProperty (color, Object); + RestoreProperty (yValue, Double); + RestoreProperty (alpha, Double); + RestoreProperty (width, Double); + RestoreProperty (dashPattern, Object); + RestoreProperty (title, Object); +#pragma clang diagnostic pop + + //AverageLine + return self; +} + +- (void) encodeWithCoder: (NSCoder *)coder { + +#define EncodeProperty(property, type) [coder encode ## type :self.property forKey:@"property" ] + EncodeProperty (enableAverageLine, Bool); + EncodeProperty (color, Object); + EncodeProperty (yValue, Float); + EncodeProperty (alpha, Float); + EncodeProperty (width, Float); + EncodeProperty (dashPattern, Object); + EncodeProperty (title, Object); +} + + + -(void) setLabel:(UILabel *)label { if (_label != label) { [_label removeFromSuperview]; diff --git a/Classes/BEMLine.h b/Classes/BEMLine.h index 82a7fa1..3fe648c 100644 --- a/Classes/BEMLine.h +++ b/Classes/BEMLine.h @@ -99,16 +99,16 @@ typedef NS_ENUM(NSUInteger, BEMLineGradientDirection) { @property (strong, nonatomic, nullable) UIColor *topColor; /// A color gradient applied to the area above the line, inside of its superview. If set, it will be drawn on top of the fill from the \p topColor property. -@property (assign, nonatomic, nullable) CGGradientRef topGradient; +@property (strong, nonatomic, nullable) __attribute__((NSObject)) CGGradientRef topGradient; /// The color of the area below the line, inside of its superview @property (strong, nonatomic, nullable) UIColor *bottomColor; /// A color gradient applied to the area below the line, inside of its superview. If set, it will be drawn on top of the fill from the \p bottomColor property. -@property (assign, nonatomic, nullable) CGGradientRef bottomGradient; +@property (strong, nonatomic, nullable) __attribute__((NSObject)) CGGradientRef bottomGradient; /// A color gradient to be applied to the line. If this property is set, it will mask (override) the \p color property. -@property (assign, nonatomic, nullable) CGGradientRef lineGradient; +@property (strong, nonatomic, nullable) __attribute__((NSObject)) CGGradientRef lineGradient; /// The drawing direction of the line gradient color @property (nonatomic) BEMLineGradientDirection lineGradientDirection; diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index 2ebac1f..b101b20 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -85,15 +85,15 @@ - (void)drawRect:(CGRect)rect { if (self.enableReferenceLines == YES) { if (self.arrayOfVerticalReferenceLinePoints.count > 0) { for (NSNumber *xNumber in self.arrayOfVerticalReferenceLinePoints) { - CGFloat xValue; + CGFloat xValue =[xNumber doubleValue]; if (self.verticalReferenceHorizontalFringeNegation != 0.0) { - if ([self.arrayOfVerticalReferenceLinePoints indexOfObject:xNumber] == 0) { // far left reference line - xValue = [xNumber floatValue] + self.verticalReferenceHorizontalFringeNegation; - } else if ([self.arrayOfVerticalReferenceLinePoints indexOfObject:xNumber] == [self.arrayOfVerticalReferenceLinePoints count]-1) { // far right reference line - xValue = [xNumber floatValue] - self.verticalReferenceHorizontalFringeNegation; - } else xValue = [xNumber floatValue]; - } else xValue = [xNumber floatValue]; - + NSUInteger index = [self.arrayOfVerticalReferenceLinePoints indexOfObject:xNumber]; + if (index == 0) { // far left reference line + xValue += self.verticalReferenceHorizontalFringeNegation; + } else if (index == [self.arrayOfVerticalReferenceLinePoints count]-1) { // far right reference line + xValue -= self.verticalReferenceHorizontalFringeNegation; + } + } CGPoint initialPoint = CGPointMake(xValue, self.frame.size.height); CGPoint finalPoint = CGPointMake(xValue, 0); @@ -178,7 +178,7 @@ - (void)drawRect:(CGRect)rect { CGContextSaveGState(ctx); CGContextAddPath(ctx, [fillTop CGPath]); CGContextClip(ctx); - CGContextDrawLinearGradient(ctx, self.topGradient, CGPointZero, CGPointMake(0, CGRectGetMaxY(fillTop.bounds)), 0); + CGContextDrawLinearGradient(ctx, self.topGradient, CGPointZero, CGPointMake(0, CGRectGetMaxY(fillTop.bounds)), (CGGradientDrawingOptions) 0); CGContextRestoreGState(ctx); } @@ -186,7 +186,7 @@ - (void)drawRect:(CGRect)rect { CGContextSaveGState(ctx); CGContextAddPath(ctx, [fillBottom CGPath]); CGContextClip(ctx); - CGContextDrawLinearGradient(ctx, self.bottomGradient, CGPointZero, CGPointMake(0, CGRectGetMaxY(fillBottom.bounds)), 0); + CGContextDrawLinearGradient(ctx, self.bottomGradient, CGPointZero, CGPointMake(0, CGRectGetMaxY(fillBottom.bounds)), (CGGradientDrawingOptions) 0); CGContextRestoreGState(ctx); } @@ -401,7 +401,7 @@ - (CALayer *)backgroundGradientLayerForLayer:(CAShapeLayer *)shapeLayer { end = CGPointMake(CGRectGetMidX(shapeLayer.bounds), CGRectGetMaxY(shapeLayer.bounds)); } - CGContextDrawLinearGradient(imageCtx, self.lineGradient, start, end, 0); + CGContextDrawLinearGradient(imageCtx, self.lineGradient, start, end, (CGGradientDrawingOptions)0); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); CALayer *gradientLayer = [CALayer layer]; diff --git a/Classes/BEMSimpleLineGraphView.h b/Classes/BEMSimpleLineGraphView.h index 3b1de6b..12917f7 100644 --- a/Classes/BEMSimpleLineGraphView.h +++ b/Classes/BEMSimpleLineGraphView.h @@ -240,7 +240,7 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView - + + + + + - - + + @@ -15,17 +18,17 @@ - + - + diff --git a/Sample Project/SimpleLineChart/ViewController.m b/Sample Project/SimpleLineChart/ViewController.m index 985040d..42d406f 100644 --- a/Sample Project/SimpleLineChart/ViewController.m +++ b/Sample Project/SimpleLineChart/ViewController.m @@ -41,9 +41,12 @@ - (void)viewDidLoad { }; // Apply the gradient to the bottom portion of the graph - self.myGraph.gradientBottom = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations); + CGGradientRef gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations); + self.myGraph.gradientBottom = gradient; + CGColorSpaceRelease(colorspace); - // Note: clang analyzer will complain about leak of gradient, but we use assign on gradient properties to avoid leak + CGGradientRelease(gradient); + // Enable and disable various graph properties and axis displays self.myGraph.enableTouchReport = YES; diff --git a/Sample Project/TestBed/ARFontPickerViewController.h b/Sample Project/TestBed/ARFontPickerViewController.h new file mode 100644 index 0000000..b1a9e4f --- /dev/null +++ b/Sample Project/TestBed/ARFontPickerViewController.h @@ -0,0 +1,46 @@ +// +// ARFontPickerViewController.h +// +// Created by Alexander Repty on 15.03.10. +// +// Copyright (c) 2010, Alexander Repty +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// Neither the name of Alexander Repty nor the names of his contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +@import UIKit; + +@class ARFontPickerViewController; + +@protocol ARFontPickerViewControllerDelegate + +- (void)fontPickerViewController:(ARFontPickerViewController *)fontPicker didSelectFont:(NSString *)fontName; + +@end + +@interface ARFontPickerViewController : UITableViewController + +@property(nonatomic,weak) id delegate; + +@end diff --git a/Sample Project/TestBed/ARFontPickerViewController.m b/Sample Project/TestBed/ARFontPickerViewController.m new file mode 100644 index 0000000..0d2c40a --- /dev/null +++ b/Sample Project/TestBed/ARFontPickerViewController.m @@ -0,0 +1,98 @@ +// +// ARFontPickerViewController.m +// +// Created by Alexander Repty on 15.03.10. +// +// Copyright (c) 2010, Alexander Repty +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// Neither the name of Alexander Repty nor the names of his contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#import "ARFontPickerViewController.h" + +static NSString * kARFontPickerViewControllerCellIdentifier = @"ARFontPickerViewControllerCellIdentifier"; + +@implementation ARFontPickerViewController + +#pragma mark - +#pragma mark UITableViewController methods + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return [[UIFont familyNames] count]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + NSString *familyName = [self fontFamilyForSection:section]; + return [[UIFont fontNamesForFamilyName:familyName] count]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return [self fontFamilyForSection:section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kARFontPickerViewControllerCellIdentifier]; + + NSString *familyName = [self fontFamilyForSection:indexPath.section]; + NSString *fontName = [self fontNameForRow:indexPath.row inFamily:familyName]; + UIFont *font = [UIFont fontWithName:fontName size:[UIFont smallSystemFontSize]]; + + cell.textLabel.text = fontName; + cell.textLabel.font = font; + + return cell; +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + if (self.delegate != nil) { + NSString *familyName = [self fontFamilyForSection:indexPath.section]; + NSString *fontName = [self fontNameForRow:indexPath.row inFamily:familyName]; + [self.delegate fontPickerViewController:self didSelectFont:fontName]; + } +} + +- (NSString *)fontFamilyForSection:(NSInteger)section { + if (section < 0) return nil; + NSUInteger realSection = (NSUInteger) section; + NSArray < NSString *> *fontNames = [UIFont familyNames]; + if (realSection < fontNames.count) { + return fontNames[realSection]; + } else { + return nil; + } +} + +- (NSString *)fontNameForRow:(NSInteger)row inFamily:(NSString *)family { + if (row < 0) return nil; + NSUInteger realRow = (NSUInteger) row; + NSArray < NSString *> *fontNames = [UIFont fontNamesForFamilyName:family]; + if (realRow < fontNames.count) { + return fontNames[realRow]; + } else { + return nil; + } +} + +@end + diff --git a/Sample Project/TestBed/AppDelegate.h b/Sample Project/TestBed/AppDelegate.h new file mode 100644 index 0000000..8429d01 --- /dev/null +++ b/Sample Project/TestBed/AppDelegate.h @@ -0,0 +1,17 @@ +// +// AppDelegate.h +// TestBed +// +// Created by Hugh Mackworth on 3/18/17. +// Copyright © 2017 Boris Emorine. All rights reserved. +// + +@import UIKit; + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + + +@end + diff --git a/Sample Project/TestBed/AppDelegate.m b/Sample Project/TestBed/AppDelegate.m new file mode 100644 index 0000000..4a8e8f9 --- /dev/null +++ b/Sample Project/TestBed/AppDelegate.m @@ -0,0 +1,59 @@ +// +// AppDelegate.m +// TestBed +// +// Created by Hugh Mackworth on 3/18/17. +// Copyright © 2017 Boris Emorine. All rights reserved. +// + +#import "AppDelegate.h" + +@interface AppDelegate () + +@end + +@implementation AppDelegate + + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder { + return YES; +} + +- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder { + return YES; +} + + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. +} + + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. +} + + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. +} + + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. +} + + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. +} + + +@end diff --git a/Sample Project/TestBed/AppIcon60x60@2x.png b/Sample Project/TestBed/AppIcon60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..5c7a53230ace7ae5f1d1c694578877b043ed4efb GIT binary patch literal 17651 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 avUuj#mhCI8C%69$iGr-E%ma*R;Qs>q8E+K; literal 0 HcmV?d00001 diff --git a/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x.png b/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon76x76@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6526a7818d5c5bd65ae79e9a76d13f2b6e8b8323 GIT binary patch literal 40011 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 RkV*gm002ovPDHLkV1f`Q8cYBH literal 0 HcmV?d00001 diff --git a/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x.png b/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/AppIcon83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3c770901c525b54653619eb4c8822ccdb07c0618 GIT binary patch literal 43497 zcmeFZ1zc6j8a_;ShlC(VH+yejv-hT3Iwho}LAqPIOF#sak`Ry*q`Q#@L6MM7>5^2w zt*6gD=iYO#=l}V|@h7lZYvx(=%royh@65cc2o)t6Tr5f~1Ox z-(9XLY{7pZI;+WuBa{wNZNPuQbhxGCjDUbm{N)P~;c*%{0)lM5m4>#9wxR;e#NL+6 z$kg80jLXB;0bUvbLC^yR|7dIGVnpjJaOe&YOFD0uNd1ah#ncd>W2wEvS1{Q33|-S~BI{GgfO7e!#oPFCfV`XlASaMd0|bKc0%7oR1mJ@K z0D|2AQucQ^5>94tgr86VoIn640HOipgMq*>9xyuq^fSt@O8yQ*(caX`-18@lf7_B@ zO8ySxE4sathP}Owu-F$9q*eK;KWQb@`Dhi5Oswp_^zRRpeqH=mNTwz*b9*OSBNt&S zTO$iIZU;LHLGE8J{;DLrUlbL;bg+e$oso^Cy@~6Wu_`Mm48Or;YGn$O0!#4oic9kX z#6bWs1PBzDl9Z5=0D{DU{89iuko4Df{4vD8Jm!{_vx~ix=dUz}EC1CqsGB+d_UcbJ zY^=UoxPy_Cv)PxCEJXLyQ2yB^{A}@G>U^~bn6tgPi@TANnV1Ef%zx^~KdSOe(|<<) z5!&6-%u+XJYnaKU@8!e*OXZS5^NU-^k>v{g`|m)TV;m zKUDqk>hG%wa)0rmHdg;H5x~UK$j-vdRG9lGT0dR*J+EJe!@p<-hIf zfuKNMpp+O8@MpI1-)rV<3Ksjv#{L!?`=S^>Jh+GPz?bkp zEdKxJ$m4fjQ5-12D!9^Lc*&MU$r4`~n|1S$=N0C=JQ zME8HkJO;P1uZr>Vz(D-pYGVK~F>y(LesKUiHvGDR{ztK&HwnLd_;I7~+aeFpmpzX! z(LL|qVPn5rL4S$vr6GLMJiI)-e0*a6#EO59js37o1)s9YSNwYv0|CFxV<7k`&$rKGkl!i>1&ISB#rSzZ5HOE42>j2U$KRwFj|PYbz6}iD zsQP9b`(1SZWtU0{2;t-Bm*ka@l=>%D{F@Ym?<>LsMfm@3k34?Af`-pyQqmy!&W99; z7XtieJ@(CteLb`W0Ds(9MlRT})qKkKn?U61{s82o(f z%Yi10_y35ENl5eai$S51P{9A)r!7C6P5(AK;HQj(f6XTNk`V$=MUeg1({%{^gciyx z1?30xi2;F9JmTU!QWE?C2oDIzFAf%i$5a1O^_SDkA7TF>IpRlHvHt}7tE&GBYvyDH zKk}7!vbX(QFUcBTk_i4~ixKW1e=pwes{YcRAC@j(ZkzruX&^r| z>Q_~NLH?Q(t(c!Q$c&egH_~$AvCG zP+AJ^xTN?cC3qyD@R07`Crwjz!6rN)w zEg`|r18*lUFIeiY13|=rAOJv00`jA3@Xi+Ju zu!fn3i-M7-*?&87|Dxr?hx(V%{!6pIK1<2S)=XH#(#o0E#mv^h2A;=8>u%&s`(ySG zt+6Mqq7{5M!pw$N+Q!Jq)q$2p)corQR$3J+4>KDlcyi#cx&8wBiwXbmcoct$2IlaZ~10IisVgO!QB-A`1$-1(QreSPTf8~2|^$hY^gm9qMm=F!T++efSM zQ(yCN@%&2zze@7^2L4&^{dL&y#G4NY`h_<Tv&T_HD%f3*7I-9G=<5ORHw) z0#AM}CJFx-sfqs(pmc6YRtvcLUn%R9DzrTq2 zy%_WUJdl6Im=^AP;KA>=$O@mG|DY@X82Ec}2LOI*qPl~SiwR$;ZO^+sOSppzp+uAN1?7 z`8QbOUjh71T={-AO8*9?{~7K(@r3-VoBg(N`&R(J6IVXS&r{0ZVxoTm?>jO5vOEFP zs#|%PeWMrpcR=5Zn~J5q-8Zgcto|6|dolX|uv+kaou7>EZ;NaGBIb8e6AbwEEctED z^gm#ICx-tnH2JTh@qY*Oow)Jw|60AjW9I)0fZvPj|8M5Q|7OAaUyb1Z2KYOv{AKs^ z>&73gmX)(5tpfZHdcVyn{T0CP&K2Iszl-G`L47BFf7=xL?|{A&Hy!xf4rqwqm{Me{Ne^w;4i4KG;%g`ru{aGe;M^VvHm)$ zBh`uC^#YnXzNJOFrThhaAIjVd%)vid+Ubl$1#vyu@Jm<@Ud~f??XS?O9>Ebe9**#Nujmu});{M@O9eZ+^vsM`nduWS- z-z1V>1*Ze)>O_e)v{<(e!jaMJVg-q7ZFJb~d$Gs7IbnF`s{a~!=~LWNgC?+Eo5el{ z^5F+FpVE5{Ucm|M9X7Rkc18z0RUTb<#!0*r(+2`HhpdnL%?;Mv-zzzbr0saD4s8mS zx<7g!pN4JG_3|#u@HtOf*R#Flo17Orja<8xdQ3A*{ik|KHXX+R_xBkVX&nR8%jZ

*y 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sample Project/TestBed/Base.lproj/Main.storyboard b/Sample Project/TestBed/Base.lproj/Main.storyboard new file mode 100644 index 0000000..42a7487 --- /dev/null +++ b/Sample Project/TestBed/Base.lproj/Main.storyboard @@ -0,0 +1,2307 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sample Project/TestBed/DetailViewController.h b/Sample Project/TestBed/DetailViewController.h new file mode 100644 index 0000000..6f561d7 --- /dev/null +++ b/Sample Project/TestBed/DetailViewController.h @@ -0,0 +1,43 @@ +// +// DetailViewController.h +// SimpleLineChart +// +// Created by Hugh Mackworth on 5/19/16. +// Copyright © 2016 Boris Emorine. All rights reserved. +// + +@import UIKit; +#import "BEMSimpleLineGraphView.h" + +@interface DetailViewController : UIViewController + +@property (weak, nonatomic) IBOutlet BEMSimpleLineGraphView *myGraph; + +- (void) addPointToGraph; +- (void) removePointFromGraph; + +- (IBAction)refresh:(id)sender; + +//data needed to implement delegate methods +//@"" or negative float or NSNotFound or FALSE to indicate don't provide a delegate method +@property (strong, nonatomic) NSString *popUpText; +@property (strong, nonatomic) NSString *popUpPrefix; +@property (strong, nonatomic) NSString *popUpSuffix; +@property (assign, nonatomic) BOOL testAlwaysDisplayPopup; +@property (assign, nonatomic) CGFloat maxValue; +@property (assign, nonatomic) CGFloat minValue; +@property (assign, nonatomic) BOOL noDataLabel; +@property (strong, nonatomic) NSString *noDataText; +@property (assign, nonatomic) CGFloat staticPaddingValue; +@property (assign, nonatomic) BOOL provideCustomView; +@property (assign, nonatomic) NSUInteger numberOfGapsBetweenLabels; +@property (assign, nonatomic) NSUInteger baseIndexForXAxis; +@property (assign, nonatomic) NSUInteger incrementIndexForXAxis; +@property (assign, nonatomic) BOOL provideIncrementPositionsForXAxis; +@property (assign, nonatomic) NSUInteger numberOfYAxisLabels; +@property (strong, nonatomic) NSString *yAxisPrefix; +@property (strong, nonatomic) NSString *yAxisSuffix; +@property (assign, nonatomic) CGFloat baseValueForYAxis; +@property (assign, nonatomic) CGFloat incrementValueForYAxis; + +@end diff --git a/Sample Project/TestBed/DetailViewController.m b/Sample Project/TestBed/DetailViewController.m new file mode 100644 index 0000000..44ecbcf --- /dev/null +++ b/Sample Project/TestBed/DetailViewController.m @@ -0,0 +1,358 @@ +// +// DetailViewController.m +// SimpleLineChart +// +// Created by Hugh Mackworth on 5/19/16. +// Copyright © 2016 Boris Emorine. All rights reserved. +// + +#import "DetailViewController.h" +#import "StatsViewController.h" +#import "BEMGraphCalculator.h" + +@interface DetailViewController () + +@property (weak, nonatomic) IBOutlet UIStepper *graphObjectIncrement; +@property (nonatomic) NSInteger previousStepperValue; + +@property (strong, nonatomic) NSMutableArray *arrayOfValues; +@property (strong, nonatomic) NSMutableArray *arrayOfDates; + +@property (strong, nonatomic) IBOutlet UILabel *labelValues; +@property (strong, nonatomic) IBOutlet UILabel *labelDates; + +@property (nonatomic) NSInteger totalNumber; + +@property (strong, nonatomic) IBOutlet UIView * customView; +@property (weak, nonatomic) IBOutlet UILabel * customViewLabel; +@end + +@implementation DetailViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.maxValue = -1.0; + self.minValue = -1.0; + self.staticPaddingValue = -1.0; + self.numberOfGapsBetweenLabels = NSNotFound; + self.baseIndexForXAxis = NSNotFound; + self.incrementIndexForXAxis = NSNotFound; + self.numberOfYAxisLabels = NSNotFound; + self.baseValueForYAxis = -1.0; + self.incrementValueForYAxis = -1.0; + // Do any additional setup after loading the view. + + + [self hydrateDatasets]; + + CGFloat sum= 0; + for (NSNumber * number in self.arrayOfValues) { + CGFloat n = number.doubleValue; + + if (n <= BEMNullGraphValue) { + sum += n; + } + } + // The labels to report the values of the graph when the user touches it + self.labelValues.text = [NSString stringWithFormat:@"%i", (int) sum]; + self.labelDates.text = @"between now and later"; + +} + +#pragma mark Data management +- (void)hydrateDatasets { + // Reset the arrays of values (Y-Axis points) and dates (X-Axis points / labels) + if (!self.arrayOfValues) self.arrayOfValues = [[NSMutableArray alloc] init]; + if (!self.arrayOfDates) self.arrayOfDates = [[NSMutableArray alloc] init]; + [self.arrayOfValues removeAllObjects]; + [self.arrayOfDates removeAllObjects]; + + self.totalNumber = 0; + NSDate *baseDate = [NSDate date]; + BOOL showNullValue = YES; + self.graphObjectIncrement.value = 9; + self.previousStepperValue = self.graphObjectIncrement.value; + + // Add objects to the array based on the stepper value + for (int i = 0; i < 9; i++) { + [self.arrayOfValues addObject:@([self getRandomFloat])]; // Random values for the graph + if (i == 0) { + [self.arrayOfDates addObject:baseDate]; // Dates for the X-Axis of the graph + } else { + [self.arrayOfDates addObject:[self dateForGraphAfterDate:self.arrayOfDates[i-1]]]; // Dates for the X-Axis of the graph + } + if (showNullValue && i == 4) { + self.arrayOfValues[i] = @(BEMNullGraphValue); + } else { + self.totalNumber = self.totalNumber + [[self.arrayOfValues objectAtIndex:i] intValue]; // All of the values added together + } + + } +} + +- (NSDate *)dateForGraphAfterDate:(NSDate *)date { + NSTimeInterval secondsInTwentyFourHours = 24 * 60 * 60; + NSDate *newDate = [date dateByAddingTimeInterval:secondsInTwentyFourHours]; + return newDate; +} + +- (NSString *)labelForDateAtIndex:(NSInteger)index { + NSDate *date = self.arrayOfDates[index]; + NSDateFormatter *df = [[NSDateFormatter alloc] init]; + df.dateFormat = @"MM/dd"; + NSString *label = [df stringFromDate:date]; + return label; +} + +#pragma mark - Graph Actions + +// Refresh the line graph using the specified properties +- (IBAction)refresh:(id)sender { + [self hydrateDatasets]; + [self.myGraph reloadGraph]; +} + +- (float)getRandomFloat { + float i1 = (float)(arc4random() % 1000000) / 100 ; + return i1; +} + +- (IBAction)addOrRemovePointFromGraph:(id)sender { + if (self.graphObjectIncrement.value > self.previousStepperValue) { + [self addPointToGraph]; + } else if (self.graphObjectIncrement.value < self.previousStepperValue) { + [self removePointFromGraph]; + } + self.previousStepperValue = self.graphObjectIncrement.value; +} + +- (void) addPointToGraph { + // Add point + [self.arrayOfValues addObject:@([self getRandomFloat])]; + NSDate *lastDate = self.arrayOfDates.count > 0 ? [self.arrayOfDates lastObject]: [NSDate date]; + NSDate *newDate = [self dateForGraphAfterDate:lastDate]; + [self.arrayOfDates addObject:newDate]; + [self.myGraph reloadGraph]; +} + +- (void) removePointFromGraph { + if (self.arrayOfValues.count > 0) { + // Remove point + [self.arrayOfValues removeObjectAtIndex:0]; + [self.arrayOfDates removeObjectAtIndex:0]; + [self.myGraph reloadGraph]; + } +} + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + [super prepareForSegue:segue sender:sender]; + + if ([segue.identifier isEqualToString:@"showStats"]) { + BEMGraphCalculator * calc = [BEMGraphCalculator sharedCalculator]; + StatsViewController *controller = segue.destinationViewController; + controller.standardDeviation = [NSString stringWithFormat:@"%.2f", [[calc calculateStandardDeviationOnGraph:self.myGraph] doubleValue]]; + controller.average = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueAverageOnGraph:self.myGraph] doubleValue]]; + controller.median = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueMedianOnGraph: self.myGraph] doubleValue]]; + controller.mode = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueModeOnGraph: self.myGraph] doubleValue]]; + controller.minimum = [NSString stringWithFormat:@"%.2f", [[calc calculateMinimumPointValueOnGraph:self.myGraph] doubleValue]]; + controller.maximum = [NSString stringWithFormat:@"%.2f", [[calc calculateMaximumPointValueOnGraph:self.myGraph] doubleValue]]; + controller.snapshotImage = [self.myGraph graphSnapshotImage]; + } +} + + +#pragma mark - SimpleLineGraph Data Source + +- (NSUInteger)numberOfPointsInLineGraph:(BEMSimpleLineGraphView *)graph { + return (int)[self.arrayOfValues count]; +} + +- (CGFloat)lineGraph:(BEMSimpleLineGraphView *)graph valueForPointAtIndex:(NSUInteger)index { + return [[self.arrayOfValues objectAtIndex:index] doubleValue]; +} + +#pragma mark - SimpleLineGraph Delegate + +-(BOOL) respondsToSelector:(SEL)aSelector { + if (aSelector == @selector(popUpTextForlineGraph:atIndex:)) { + return self.popUpText.length > 0; + } else if (aSelector == @selector(popUpPrefixForlineGraph:)) { + return self.popUpPrefix.length > 0; + } else if (aSelector == @selector(popUpSuffixForlineGraph:)) { + return self.popUpSuffix.length > 0; + } else if (aSelector == @selector(lineGraph:alwaysDisplayPopUpAtIndex:)) { + return self.testAlwaysDisplayPopup; + } else if (aSelector == @selector(maxValueForLineGraph:)) { + return self.maxValue >= 0.0; + } else if (aSelector == @selector(minValueForLineGraph:)) { + return self.minValue >= 0.0; + } else if (aSelector == @selector(noDataLabelTextForLineGraph:)) { + return self.noDataText.length > 0; + } else if (aSelector == @selector(staticPaddingForLineGraph:)) { + return self.staticPaddingValue > 0; + } else if (aSelector == @selector(popUpViewForLineGraph:)) { + return self.provideCustomView; + } else if (aSelector == @selector(lineGraph:modifyPopupView:forIndex:)) { + return self.provideCustomView; + } else if (aSelector == @selector(numberOfGapsBetweenLabelsOnLineGraph:)) { + return self.numberOfGapsBetweenLabels != NSNotFound; + } else if (aSelector == @selector(baseIndexForXAxisOnLineGraph:)) { + return self.baseIndexForXAxis != NSNotFound; + } else if (aSelector == @selector(incrementIndexForXAxisOnLineGraph:)) { + return self.incrementIndexForXAxis != NSNotFound; + } else if (aSelector == @selector(incrementPositionsForXAxisOnLineGraph:)) { + return self.provideIncrementPositionsForXAxis; + } else if (aSelector == @selector(numberOfYAxisLabelsOnLineGraph:)) { + return self.numberOfYAxisLabels != NSNotFound; + } else if (aSelector == @selector(yAxisPrefixOnLineGraph:)) { + return self.yAxisPrefix.length > 0; + } else if (aSelector == @selector(yAxisSuffixOnLineGraph:)) { + return self.yAxisSuffix.length > 0; + } else if (aSelector == @selector(baseValueForYAxisOnLineGraph:)) { + return self.baseValueForYAxis >= 0; + } else if (aSelector == @selector(incrementValueForYAxisOnLineGraph:)) { + return self.baseValueForYAxis >= 0.0; + } else { + return [super respondsToSelector:aSelector]; + } +} + + +- (NSString *)lineGraph:(BEMSimpleLineGraphView *)graph labelOnXAxisForIndex:(NSUInteger)index { + NSString *label = [self labelForDateAtIndex:index]; + return [label stringByReplacingOccurrencesOfString:@" " withString:@"\n"]; +} + +- (NSString *)popUpSuffixForlineGraph:(BEMSimpleLineGraphView *)graph { + return self.popUpSuffix; +} + +- (NSString *)popUpPrefixForlineGraph:(BEMSimpleLineGraphView *)graph { + return self.popUpPrefix; +} + +-(NSString *) popUpTextForlineGraph:(BEMSimpleLineGraphView *)graph atIndex:(NSUInteger)index { + if (!self.popUpText) return @"Invalid format string"; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" + return [NSString stringWithFormat: self.popUpText, index]; +#pragma clang diagnostic pop + +} + +- (BOOL)lineGraph:(BEMSimpleLineGraphView *)graph alwaysDisplayPopUpAtIndex:(NSUInteger)index { + return (index % 3 != 0); +} + +- (CGFloat)maxValueForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.maxValue; +} + +- (CGFloat)minValueForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.minValue; +} + +- (BOOL)noDataLabelEnableForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.noDataLabel; +} +- (NSString *)noDataLabelTextForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.noDataText; +} + +- (CGFloat)staticPaddingForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.staticPaddingValue; +} + +- (UIView *)popUpViewForLineGraph:(BEMSimpleLineGraphView *)graph { + return self.customView; +} + +- (void)lineGraph:(BEMSimpleLineGraphView *)graph modifyPopupView:(UIView *)popupView forIndex:(NSUInteger)index { + NSAssert (popupView == self.customView, @"View problem"); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" + if (!self.myGraph.formatStringForValues) return; + self.customViewLabel.text = [NSString stringWithFormat:self.myGraph.formatStringForValues, [self lineGraph:graph valueForPointAtIndex:index] ]; +#pragma pop +} + +//----- X AXIS -----// + +- (NSUInteger)numberOfGapsBetweenLabelsOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.numberOfGapsBetweenLabels; +} + +- (NSUInteger)baseIndexForXAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.baseIndexForXAxis; +} + +- (NSUInteger)incrementIndexForXAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.incrementIndexForXAxis; +} + +- (NSArray *)incrementPositionsForXAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { + NSMutableArray * positions = [NSMutableArray array]; + NSUInteger index = 1; + while (index < self.arrayOfValues.count) { + if (arc4random() % 4 == 0) [positions addObject:@(index)]; + index +=1; + } + return positions; +} + +//----- Y AXIS -----// + +- (NSUInteger)numberOfYAxisLabelsOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.numberOfYAxisLabels; +} + +- (NSString *)yAxisPrefixOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.yAxisPrefix; +} + +- (NSString *)yAxisSuffixOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.yAxisSuffix; +} + +- (CGFloat)baseValueForYAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.baseValueForYAxis; +} + +- (CGFloat)incrementValueForYAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { + return self.incrementValueForYAxis; +} + +#pragma mark Touch handling + +- (void)lineGraph:(BEMSimpleLineGraphView *)graph didTouchGraphWithClosestIndex:(NSUInteger)index { + self.labelValues.text = [NSString stringWithFormat:@"%@", [self.arrayOfValues objectAtIndex:index]]; + self.labelDates.text = [NSString stringWithFormat:@"in %@", [self labelForDateAtIndex:index]]; +} + +- (void)lineGraph:(BEMSimpleLineGraphView *)graph didReleaseTouchFromGraphWithClosestIndex:(CGFloat)index { + [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.labelValues.alpha = 0.0; + self.labelDates.alpha = 0.0; + } completion:^(BOOL finished) { + self.labelValues.text = [NSString stringWithFormat:@"%i", [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph].intValue]; + self.labelDates.text = [NSString stringWithFormat:@"between %@ and %@", [self labelForDateAtIndex:0], [self labelForDateAtIndex:self.arrayOfDates.count - 1]]; + + [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.labelValues.alpha = 1.0; + self.labelDates.alpha = 1.0; + } completion:nil]; + }]; +} + +- (void)lineGraphDidFinishLoading:(BEMSimpleLineGraphView *)graph { + if (self.arrayOfValues.count > 0) { + self.labelValues.text = [NSString stringWithFormat:@"%i", [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph].intValue]; + self.labelDates.text = [NSString stringWithFormat:@"between %@ and %@", [self labelForDateAtIndex:0], [self labelForDateAtIndex:self.arrayOfDates.count - 1]]; + } else { + self.labelValues.text = @"No data"; + self.labelDates.text = @""; + } +} + +@end diff --git a/Sample Project/TestBed/Info.plist b/Sample Project/TestBed/Info.plist new file mode 100644 index 0000000..f4b34c9 --- /dev/null +++ b/Sample Project/TestBed/Info.plist @@ -0,0 +1,39 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Sample Project/TestBed/MasterViewController.h b/Sample Project/TestBed/MasterViewController.h new file mode 100644 index 0000000..2df18db --- /dev/null +++ b/Sample Project/TestBed/MasterViewController.h @@ -0,0 +1,30 @@ +// +// MasterViewController.h +// TestBed2 +// +// Created by Hugh Mackworth on 5/18/16. +// Copyright © 2016 Boris Emorine. All rights reserved. +// + +@import UIKit; + + + +@class DetailViewController; + +@interface MasterViewController : UITableViewController + +@property (strong, nonatomic) DetailViewController *detailViewController; + + +@end + +@interface CustomTableViewCell : UITableViewCell + +@property (nonatomic) IBOutlet UILabel * title; + +@end + +@implementation CustomTableViewCell + +@end diff --git a/Sample Project/TestBed/MasterViewController.m b/Sample Project/TestBed/MasterViewController.m new file mode 100644 index 0000000..fd78e40 --- /dev/null +++ b/Sample Project/TestBed/MasterViewController.m @@ -0,0 +1,1014 @@ +// +// MasterViewController.m +// TestBed2 +// +// Created by Hugh Mackworth on 5/18/16. +// Copyright © 2016 Boris Emorine. All rights reserved. +// + +#import "MasterViewController.h" +#import "DetailViewController.h" +#import "ARFontPickerViewController.h" + +//some convenience extensions for setting and reading +@interface UITextField (Numbers) +@property (nonatomic) CGFloat floatValue; +@property (nonatomic) NSUInteger intValue; + +@end + +@implementation UITextField (Numbers) + +-(void) setFloatValue:(CGFloat) num { + if (num < 0.0) { + self.text = @""; + } else if (num >= NSNotFound ) { + self.text = @"oopsf"; + } else { + self.text = [NSString stringWithFormat:@"%0.1f",num]; + } +} + +-(void) setIntValue:(NSUInteger) num { + if (num == NSNotFound ) { + self.text = @""; + } else if (num == (NSUInteger)-1 ) { + self.text = @"oops"; + }else { + self.text = [NSString stringWithFormat:@"%d",(int)num]; + } +} + +-(CGFloat) floatValue { + if (self.text.length ==0) { + return -1.0; + } else { + return (CGFloat) self.text.floatValue; + } +} + +-(NSUInteger) intValue { + if (self.text.length ==0) { + return NSNotFound; + } else { + return (NSUInteger) self.text.integerValue; + } + +} + +@end + +@interface UIButton (Switch) +@property (nonatomic) BOOL on; +@end\ + +@implementation UIButton (Switch) +static NSString * checkOff = @"☐"; +static NSString * checkOn = @"☒"; + +-(void) setOn: (BOOL) on { + [self setTitle: (on ? checkOn : checkOff) forState:UIControlStateNormal]; +} + +-(BOOL) on { + if (!self.currentTitle) return NO; + return [checkOff isEqualToString: ( NSString * _Nonnull )self.currentTitle ]; +} + +@end + + +@interface MasterViewController () + +@property (weak, nonatomic) IBOutlet BEMSimpleLineGraphView *myGraph; + +@property (strong, nonatomic) NSDictionary *methodList; + +@property (strong, nonatomic) IBOutlet UITextField *widthLine; +@property (strong, nonatomic) IBOutlet UITextField *staticPaddingField; +@property (strong, nonatomic) IBOutlet UISwitch *bezierSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *interpolateNullValuesSwitch; + +@property (strong, nonatomic) IBOutlet UISwitch *xAxisSwitch; +@property (strong, nonatomic) IBOutlet UITextField *numberOfGapsBetweenLabelsField; +@property (strong, nonatomic) IBOutlet UITextField *baseIndexForXAxisField; +@property (strong, nonatomic) IBOutlet UITextField *incrementIndexForXAxisField; +@property (strong, nonatomic) IBOutlet UISwitch *arrayOfIndicesForXAxis; + +@property (strong, nonatomic) IBOutlet UISwitch *yAxisSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *yAxisRightSwitch; +@property (strong, nonatomic) IBOutlet UITextField *minValueField; +@property (strong, nonatomic) IBOutlet UITextField *maxValueField; +@property (strong, nonatomic) IBOutlet UITextField *numberofYAxisField; +@property (strong, nonatomic) IBOutlet UITextField *yAxisPrefixField; +@property (strong, nonatomic) IBOutlet UITextField *yAxisSuffixField; +@property (strong, nonatomic) IBOutlet UITextField *baseValueForYAxis; +@property (strong, nonatomic) IBOutlet UITextField *incrementValueForYAxis; + +@property (strong, nonatomic) IBOutlet UISwitch *enableAverageLineSwitch; +@property (strong, nonatomic) IBOutlet UITextField *averageLineTitleField; +@property (strong, nonatomic) IBOutlet UITextField *averageLineWidthField; + +@property (strong, nonatomic) IBOutlet UITextField *widthReferenceLinesField; +@property (strong, nonatomic) IBOutlet UISwitch *xRefLinesSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *yRefLinesSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *enableReferenceAxisSwitch; +@property (strong, nonatomic) IBOutlet CustomTableViewCell *frameReferenceAxesCell; +@property (strong, nonatomic) IBOutlet UIButton *leftFrameButton; +@property (strong, nonatomic) IBOutlet UIButton *rightFrameButton; +@property (strong, nonatomic) IBOutlet UIButton *topFrameButton; +@property (strong, nonatomic) IBOutlet UIButton *bottomFrameButton; + +@property (strong, nonatomic) IBOutlet UISwitch *displayDotsSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *displayDotsOnlySwitch; +@property (strong, nonatomic) IBOutlet UITextField *sizePointField; +@property (strong, nonatomic) IBOutlet UISwitch *displayLabelsSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *popupReportSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *testDisplayPopupCallBack; +@property (strong, nonatomic) IBOutlet UITextField *labelTextFormat; +@property (strong, nonatomic) IBOutlet UITextField *popupLabelPrefix; +@property (strong, nonatomic) IBOutlet UITextField *poupLabelSuffix; +@property (strong, nonatomic) IBOutlet UISwitch *enableCustomViewSwitch; +@property (strong, nonatomic) IBOutlet UITextField *noDataLabelTextField; +@property (strong, nonatomic) IBOutlet UISwitch *enableNoDataLabelSwitch; + +@property (strong, nonatomic) IBOutlet UIButton *animationGraphStyleButton; +@property (strong, nonatomic) IBOutlet UITextField *animationEntranceTime; +@property (strong, nonatomic) IBOutlet UISwitch *dotsWhileAnimateSwitch; +@property (strong, nonatomic) IBOutlet UISwitch *touchReportSwitch; +@property (strong, nonatomic) IBOutlet UITextField *widthTouchInputLineField; + +@property (strong, nonatomic) IBOutlet UIButton *fontNameButton; +@property (strong, nonatomic) IBOutlet UITextField *fontSizeField; +@property (strong, nonatomic) IBOutlet UITextField *numberFormatField; + + +@end + +@implementation MasterViewController + +static NSString * enableTouchReport = @"enableTouchReport"; +static NSString * lineChartPrefix = @"lineChart"; + +/*-(void) loadDefaults { + //shorthands + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + BEMSimpleLineGraphView * myGraph = self.myGraph; + + NSString * fontName = [defaults stringForKey: @"labelFontName"]; + self.fontSizeField.text = [defaults stringForKey: @"labelFontSize"]; + [self updateFont:fontName]; + // myGraph.labelFont = [self fontNamed: fontName ofSize: self.fontSize]; + + // Set Animation Values + myGraph.animationGraphEntranceTime = [defaults floatForKey: @"animationGraphEntranceTime"]; + +// // Set Color Values +// myGraph.colorXaxisLabel = [defaults colorForKey: @"colorXaxisLabel"]; +// myGraph.colorYaxisLabel = [defaults colorForKey: @"colorYaxisLabel"]; +// myGraph.colorTop = [defaults colorForKey: @"colorTop"]; +// myGraph.colorLine = [defaults colorForKey: @"colorLine"]; +// myGraph.colorBottom = [defaults colorForKey: @"colorBottom"]; +// myGraph.colorPoint = [defaults colorForKey: @"colorPoint"]; +// myGraph.colorTouchInputLine = [defaults colorForKey: @"colorTouchInputLine"]; +// myGraph.colorBackgroundPopUplabel = [defaults colorForKey: @"colorBackgroundPopUplabel"]; +// myGraph.colorBackgroundYaxis = [defaults colorForKey: @"colorBackgroundYaxis"]; +// myGraph.colorBackgroundXaxis = [defaults colorForKey: @"colorBackgroundXaxis"]; +// myGraph.averageLine.color = [defaults colorForKey: @"averageLine.color"]; + + // Set Alpha Values + myGraph.alphaTop = [defaults floatForKey: @"alphaTop"]; + myGraph.alphaBottom = [defaults floatForKey: @"alphaBottom"]; + myGraph.alphaLine = [defaults floatForKey: @"alphaLine"]; + myGraph.alphaTouchInputLine = [defaults floatForKey: @"alphaTouchInputLine"]; + myGraph.alphaBackgroundXaxis = [defaults floatForKey: @"alphaBackgroundXaxis"]; + myGraph.alphaBackgroundYaxis = [defaults floatForKey: @"alphaBackgroundYaxis"]; + myGraph.averageLine.alpha = [defaults floatForKey: @"alpha"]; + + // Set Size Values + myGraph.widthLine = [defaults floatForKey: @"widthLine"]; + myGraph.widthReferenceLines = [defaults floatForKey: @"widthReferenceLines"]; + myGraph.sizePoint = [defaults floatForKey: @"sizePoint"]; + myGraph.widthTouchInputLine = [defaults floatForKey: @"widthTouchInputLine"]; + + // Set Default Feature Values + myGraph.enableTouchReport = [defaults boolForKey: @"enableTouchReport"]; + myGraph.enablePopUpReport = [defaults boolForKey: @"enablePopUpReport"]; + myGraph.enableBezierCurve = [defaults boolForKey: @"enableBezierCurve"]; + myGraph.enableXAxisLabel = [defaults boolForKey: @"enableXAxisLabel"]; + myGraph.enableYAxisLabel = [defaults boolForKey: @"enableYAxisLabel"]; + myGraph.autoScaleYAxis = [defaults boolForKey: @"autoScaleYAxis"]; + myGraph.alwaysDisplayDots = [defaults boolForKey: @"alwaysDisplayDots"]; + myGraph.alwaysDisplayPopUpLabels = [defaults boolForKey: @"alwaysDisplayPopUpLabels"]; + myGraph.enableLeftReferenceAxisFrameLine = [defaults boolForKey: @"enableLeftReferenceAxisFrameLine"]; + myGraph.enableBottomReferenceAxisFrameLine = [defaults boolForKey: @"enableBottomReferenceAxisFrameLine"]; + myGraph.interpolateNullValues = [defaults boolForKey: @"interpolateNullValues"]; + myGraph.displayDotsOnly = [defaults boolForKey: @"displayDotsOnly"]; + myGraph.displayDotsWhileAnimating = [defaults boolForKey: @"displayDotsWhileAnimating"]; + + myGraph.touchReportFingersRequired = [defaults integerForKey: @"touchReportFingersRequired"]; + myGraph.formatStringForValues = [defaults stringForKey: @"formatStringForValues"]; + + // Initialize BEM Objects + // myGraph.averageLine = [defaults boolForKey: @"averageLine"]; + + + +} + + +-(void) saveDefaults { + //shorthands + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + BEMSimpleLineGraphView * myGraph = self.myGraph; + + [defaults setObject: self.fontNameButton.titleLabel.text forKey: @"labelFontName"]; + [defaults setObject: self.fontSizeField.text forKey: @"labelFontSize"]; + + // Set Animation Values + [defaults setFloat: myGraph.animationGraphEntranceTime forKey: @"animationGraphEntranceTime"]; + +// // Set Color Values +// [defaults setColor: myGraph.colorXaxisLabel forKey: @"colorXaxisLabel"]; +// [defaults setColor: myGraph.colorYaxisLabel forKey: @"colorYaxisLabel"]; +// [defaults setColor: myGraph.colorTop forKey: @"colorTop"]; +// [defaults setColor: myGraph.colorLine forKey: @"colorLine"]; +// [defaults setColor: myGraph.colorBottom forKey: @"colorBottom"]; +// [defaults setColor: myGraph.colorPoint forKey: @"colorPoint"]; +// [defaults setColor: myGraph.colorTouchInputLine forKey: @"colorTouchInputLine"]; +// [defaults setColor: myGraph.colorBackgroundPopUplabel forKey: @"colorBackgroundPopUplabel"]; +// [defaults setColor: myGraph.colorBackgroundYaxis forKey: @"colorBackgroundYaxis"]; +// [defaults setColor: myGraph.colorBackgroundXaxis forKey: @"colorBackgroundXaxis"]; +// myGraph.averageLine.color = [defaults colorForKey: @"averageLine.color"]; + + // Set Alpha Values + [defaults setFloat: myGraph.alphaTop forKey: @"alphaTop"]; + [defaults setFloat: myGraph.alphaBottom forKey: @"alphaBottom"]; + [defaults setFloat: myGraph.alphaLine forKey: @"alphaLine"]; + [defaults setFloat: myGraph.alphaTouchInputLine forKey: @"alphaTouchInputLine"]; + [defaults setFloat: myGraph.alphaBackgroundXaxis forKey: @"alphaBackgroundXaxis"]; + [defaults setFloat: myGraph.alphaBackgroundYaxis forKey: @"alphaBackgroundYaxis"]; + myGraph.averageLine.alpha = [defaults floatForKey: @"alpha"]; + + // Set Size Values + [defaults setFloat: myGraph.widthLine forKey: @"widthLine"]; + [defaults setFloat: myGraph.widthReferenceLines forKey: @"widthReferenceLines"]; + [defaults setFloat: myGraph.sizePoint forKey: @"sizePoint"]; + [defaults setFloat: myGraph.widthTouchInputLine forKey: @"widthTouchInputLine"]; + + // Set Default Feature Values + [defaults setBool: myGraph.enableTouchReport forKey: @"enableTouchReport"]; + [defaults setBool: myGraph.enablePopUpReport forKey: @"enablePopUpReport"]; + [defaults setBool: myGraph.enableBezierCurve forKey: @"enableBezierCurve"]; + [defaults setBool: myGraph.enableXAxisLabel forKey: @"enableXAxisLabel"]; + [defaults setBool: myGraph.enableYAxisLabel forKey: @"enableYAxisLabel"]; + [defaults setBool: myGraph.autoScaleYAxis forKey: @"autoScaleYAxis"]; + [defaults setBool: myGraph.alwaysDisplayDots forKey: @"alwaysDisplayDots"]; + [defaults setBool: myGraph.alwaysDisplayPopUpLabels forKey: @"alwaysDisplayPopUpLabels"]; + [defaults setBool: myGraph.enableLeftReferenceAxisFrameLine forKey: @"enableLeftReferenceAxisFrameLine"]; + [defaults setBool: myGraph.enableBottomReferenceAxisFrameLine forKey: @"enableBottomReferenceAxisFrameLine"]; + [defaults setBool: myGraph.interpolateNullValues forKey: @"interpolateNullValues"]; + [defaults setBool: myGraph.displayDotsOnly forKey: @"displayDotsOnly"]; + [defaults setBool: myGraph.displayDotsWhileAnimating forKey: @"displayDotsWhileAnimating"]; + // [defaults setBool: myGraph.averageLine != nil forKey:<#(nonnull NSString *)#>] + + [defaults setInteger: myGraph.touchReportFingersRequired forKey: @"touchReportFingersRequired"]; + [defaults setObject: myGraph.formatStringForValues forKey: @"formatStringForValues"]; +} +*/ + +- (void)decodeRestorableStateWithCoder:(NSCoder *)coder { + + [super decodeRestorableStateWithCoder:coder]; + + #define RestoreProperty(property, type) \ + if ([coder containsValueForKey:@#property]) { \ + self.myGraph.property = [coder decode ## type ##ForKey:@#property ]; \ + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" + + RestoreProperty (labelFont, Object); + RestoreProperty (animationGraphEntranceTime, Double); + RestoreProperty (animationGraphStyle, Integer); + + RestoreProperty (colorBottom, Object); + RestoreProperty (colorTop, Object); + RestoreProperty (colorLine, Object); + RestoreProperty (colorReferenceLines, Object); + RestoreProperty (colorPoint, Object); + RestoreProperty (colorTouchInputLine, Object); + RestoreProperty (colorXaxisLabel, Object); + RestoreProperty (colorYaxisLabel, Object); + RestoreProperty (colorBackgroundYaxis, Object); + RestoreProperty (colorBackgroundXaxis, Object); + RestoreProperty (colorBackgroundPopUplabel, Object); + RestoreProperty (noDataLabelColor, Object); + RestoreProperty(noDataLabelFont, Object); + //Can't handle: gradientBottom, gradientTop, gradientLine + RestoreProperty (gradientLineDirection, Float); + + RestoreProperty (alphaBottom, Double); + RestoreProperty (alphaTop, Double); + RestoreProperty (alphaLine, Double); + RestoreProperty (alphaTouchInputLine, Double); + RestoreProperty (alphaBackgroundXaxis, Double); + RestoreProperty (alphaBackgroundYaxis, Double); + + RestoreProperty (widthLine, Double); + RestoreProperty (widthReferenceLines, Double); + RestoreProperty (sizePoint, Double); + RestoreProperty (widthTouchInputLine, Double); + + RestoreProperty (enableTouchReport, Bool); + RestoreProperty (enablePopUpReport, Bool); + RestoreProperty (enableBezierCurve, Bool); + RestoreProperty (enableXAxisLabel, Bool); + RestoreProperty (enableYAxisLabel, Bool); + RestoreProperty (autoScaleYAxis, Bool); + RestoreProperty (alwaysDisplayDots, Bool); + RestoreProperty (alwaysDisplayPopUpLabels, Bool); + RestoreProperty (enableReferenceXAxisLines, Bool); + RestoreProperty (enableReferenceYAxisLines, Bool); + RestoreProperty (enableReferenceAxisFrame, Bool); + RestoreProperty (enableLeftReferenceAxisFrameLine, Bool); + RestoreProperty (enableBottomReferenceAxisFrameLine, Bool); + RestoreProperty (enableTopReferenceAxisFrameLine, Bool); + RestoreProperty (enableRightReferenceAxisFrameLine, Bool); + [self updateReferenceAxisFrame:self.myGraph.enableReferenceAxisFrame]; + RestoreProperty (interpolateNullValues, Bool); + RestoreProperty (displayDotsOnly, Bool); + RestoreProperty (displayDotsWhileAnimating, Bool); + + RestoreProperty (touchReportFingersRequired, Int); + RestoreProperty (formatStringForValues, Object); + RestoreProperty (lineDashPatternForReferenceXAxisLines, Object); + RestoreProperty (lineDashPatternForReferenceYAxisLines, Object); + + if ([coder containsValueForKey:@"averageLine.enableAverageLine" ]) { + self.myGraph.averageLine = [[BEMAverageLine alloc] initWithCoder:coder]; + RestoreProperty (averageLine.enableAverageLine, Bool); + RestoreProperty (averageLine.color, Object); + RestoreProperty (averageLine.yValue, Double); + RestoreProperty (averageLine.alpha, Double); + RestoreProperty (averageLine.width, Double); + RestoreProperty (averageLine.dashPattern, Object); + RestoreProperty (averageLine.title, Object); + } +#define RestoreVCProperty(property, type) \ +if ([coder containsValueForKey:@#property]) { \ +self.detailViewController.property = [coder decode ## type ##ForKey:@#property ]; \ +} + RestoreVCProperty(popUpText, Object); + RestoreVCProperty(popUpPrefix, Object); + RestoreVCProperty(popUpSuffix, Object); + RestoreVCProperty(testAlwaysDisplayPopup, Bool); + RestoreVCProperty(maxValue, Double); + RestoreVCProperty(minValue, Double); + RestoreVCProperty(noDataLabel, Bool); + RestoreVCProperty(noDataText, Object); + RestoreVCProperty(staticPaddingValue, Double); + RestoreVCProperty(provideCustomView, Bool); + RestoreVCProperty(numberOfGapsBetweenLabels, Integer); + RestoreVCProperty(baseIndexForXAxis, Integer); + RestoreVCProperty(incrementIndexForXAxis, Integer); + RestoreVCProperty(provideIncrementPositionsForXAxis, Bool); + RestoreVCProperty(numberOfYAxisLabels, Integer); + RestoreVCProperty(yAxisPrefix, Object); + RestoreVCProperty(yAxisSuffix, Object); + RestoreVCProperty(baseValueForYAxis, Double); + RestoreVCProperty(incrementValueForYAxis, Double); +} +#pragma clang diagnostic pop + +- (void)encodeRestorableStateWithCoder:(NSCoder *)coder { + [super encodeRestorableStateWithCoder:coder]; + +#define EncodeProperty(property, type) [coder encode ## type: self.myGraph.property forKey:@#property] + + if (self.myGraph.labelFont && ![self.myGraph.labelFont isEqual:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]] ) { + EncodeProperty (labelFont, Object); + } + EncodeProperty (animationGraphEntranceTime, Float); + EncodeProperty (animationGraphStyle, Integer); + + EncodeProperty (colorBottom, Object); + EncodeProperty (colorTop, Object); + EncodeProperty (colorLine, Object); + EncodeProperty (colorReferenceLines, Object); + EncodeProperty (colorPoint, Object); + EncodeProperty (colorTouchInputLine, Object); + EncodeProperty (colorXaxisLabel, Object); + EncodeProperty (colorYaxisLabel, Object); + EncodeProperty (colorBackgroundYaxis, Object); + EncodeProperty (colorBackgroundXaxis, Object); + EncodeProperty (colorBackgroundPopUplabel, Object); + EncodeProperty (noDataLabelColor, Object); + EncodeProperty(noDataLabelFont, Object); + //Can't handle: gradientBottom, gradientTop, gradientLine + EncodeProperty (gradientLineDirection, Float); + + EncodeProperty (alphaBottom, Float); + EncodeProperty (alphaTop, Float); + EncodeProperty (alphaLine, Float); + EncodeProperty (alphaTouchInputLine, Float); + EncodeProperty (alphaBackgroundXaxis, Float); + EncodeProperty (alphaBackgroundYaxis, Float); + + EncodeProperty (widthLine, Float); + EncodeProperty (widthReferenceLines, Float); + EncodeProperty (sizePoint, Float); + EncodeProperty (widthTouchInputLine, Float); + + EncodeProperty (enableTouchReport, Bool); + EncodeProperty (enablePopUpReport, Bool); + EncodeProperty (enableBezierCurve, Bool); + EncodeProperty (enableXAxisLabel, Bool); + EncodeProperty (enableYAxisLabel, Bool); + EncodeProperty (autoScaleYAxis, Bool); + EncodeProperty (alwaysDisplayDots, Bool); + EncodeProperty (alwaysDisplayPopUpLabels, Bool); + EncodeProperty (enableReferenceXAxisLines, Bool); + EncodeProperty (enableReferenceYAxisLines, Bool); + EncodeProperty (enableReferenceAxisFrame, Bool); + EncodeProperty (enableLeftReferenceAxisFrameLine, Bool); + EncodeProperty (enableBottomReferenceAxisFrameLine, Bool); + EncodeProperty (enableTopReferenceAxisFrameLine, Bool); + EncodeProperty (enableRightReferenceAxisFrameLine, Bool); + EncodeProperty (interpolateNullValues, Bool); + EncodeProperty (displayDotsOnly, Bool); + EncodeProperty (displayDotsWhileAnimating, Bool); + + EncodeProperty (touchReportFingersRequired, Integer); + EncodeProperty (formatStringForValues, Object); + EncodeProperty (lineDashPatternForReferenceXAxisLines, Object); + EncodeProperty (lineDashPatternForReferenceYAxisLines, Object); + + if (self.myGraph.averageLine) { + EncodeProperty (averageLine.enableAverageLine, Bool); + EncodeProperty (averageLine.color, Object); + EncodeProperty (averageLine.yValue, Float); + EncodeProperty (averageLine.alpha, Float); + EncodeProperty (averageLine.width, Float); + EncodeProperty (averageLine.dashPattern, Object); + EncodeProperty (averageLine.title, Object); + } + +#define EncodeVCProperty(property, type) [coder encode ## type: self.detailViewController.property forKey:@#property] + + EncodeVCProperty(popUpText, Object); + EncodeVCProperty(popUpPrefix, Object); + EncodeVCProperty(popUpSuffix, Object); + EncodeVCProperty(testAlwaysDisplayPopup, Bool); + EncodeVCProperty(maxValue, Float); + EncodeVCProperty(minValue, Float); + EncodeVCProperty(noDataLabel, Bool); + EncodeVCProperty(noDataText, Object); + EncodeVCProperty(staticPaddingValue, Float); + EncodeVCProperty(provideCustomView, Bool); + EncodeVCProperty(numberOfGapsBetweenLabels, Integer); + EncodeVCProperty(baseIndexForXAxis, Integer); + EncodeVCProperty(incrementIndexForXAxis, Integer); + EncodeVCProperty(provideIncrementPositionsForXAxis, Bool); + EncodeVCProperty(numberOfYAxisLabels, Integer); + EncodeVCProperty(yAxisPrefix, Object); + EncodeVCProperty(yAxisSuffix, Object); + EncodeVCProperty(baseValueForYAxis, Float); + EncodeVCProperty(incrementValueForYAxis, Float); + +} + +//- (void)saveGraphView{ +// NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.myGraph]; +// [[NSUserDefaults standardUserDefaults] setObject:encodedObject forKey:@"graphView"]; +//} +// +//- (BEMSimpleLineGraphView *)loadGraphView { +// NSData *encodedObject = [[NSUserDefaults standardUserDefaults] objectForKey:@"graphView"]; +// BEMSimpleLineGraphView *graphView = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject]; +// return graphView; +//} +// +CGGradientRef createGradient () { + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); + size_t num_locations = 2; + CGFloat locations[2] = { 0.0, 1.0 }; + CGFloat components[8] = { + 1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, 0.0 + }; + CGGradientRef result = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations); + CGColorSpaceRelease(colorspace); + return result; +} + + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. + self.navigationItem.leftBarButtonItem = self.editButtonItem; + + self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; + [self.detailViewController loadViewIfNeeded]; + self.myGraph = self.detailViewController.myGraph; + if (!self.myGraph.averageLine) { // Draw an average line + self.myGraph.averageLine.enableAverageLine = YES; + self.myGraph.averageLine.alpha = 0.6; + self.myGraph.averageLine.color = [UIColor darkGrayColor]; + self.myGraph.averageLine.width = 2.5; + self.myGraph.averageLine.dashPattern = @[@(2),@(2)]; + self.myGraph.averageLine.title = @"Average"; + } + // Apply the gradient to the bottom portion of the graph + CGGradientRef gradient = createGradient(); + self.myGraph.gradientBottom = gradient; + CGGradientRelease(gradient); + +// [[NSNotificationCenter defaultCenter] addObserver:self +// selector:@selector(saveGraphView) +// name:UIApplicationDidEnterBackgroundNotification +// object:nil]; + self.widthLine.floatValue = self.myGraph.widthLine; + self.staticPaddingField.floatValue = self.detailViewController.staticPaddingValue; + self.bezierSwitch.on = self.myGraph.enableBezierCurve; + self.interpolateNullValuesSwitch.on = self.myGraph.interpolateNullValues; + + self.xAxisSwitch.on = self.myGraph.enableXAxisLabel; + self.numberOfGapsBetweenLabelsField.intValue = self.detailViewController.numberOfGapsBetweenLabels; + self.baseIndexForXAxisField.floatValue = self.detailViewController.baseValueForYAxis; + self.incrementIndexForXAxisField.intValue = self.detailViewController.incrementIndexForXAxis; + self.arrayOfIndicesForXAxis.on = self.detailViewController.provideIncrementPositionsForXAxis; + + self.yAxisSwitch.on = self.myGraph.enableYAxisLabel; + self.yAxisRightSwitch.on = self.myGraph.positionYAxisRight; + self.minValueField.floatValue = self.detailViewController.minValue; + self.maxValueField.floatValue = self.detailViewController.maxValue; + self.numberofYAxisField.intValue = self.detailViewController.numberOfYAxisLabels; + self.yAxisPrefixField.text = self.detailViewController.yAxisPrefix; + self.yAxisSuffixField.text = self.detailViewController.yAxisSuffix; + self.baseValueForYAxis.floatValue = self.detailViewController.baseValueForYAxis; + self.incrementIndexForXAxisField.floatValue = self.detailViewController.incrementValueForYAxis; + + self.enableAverageLineSwitch.on = self.myGraph.averageLine.enableAverageLine; + self.averageLineTitleField.text = self.myGraph.averageLine.title; + self.averageLineWidthField.floatValue = self.myGraph.averageLine.width; + + self.widthReferenceLinesField.floatValue = self.myGraph.widthReferenceLines; + self.xRefLinesSwitch.on = self.myGraph.enableReferenceXAxisLines; + self.yRefLinesSwitch.on = self.myGraph.enableReferenceYAxisLines; + self.enableReferenceAxisSwitch.on = self.myGraph.enableReferenceAxisFrame; + [self updateReferenceAxisFrame:self.myGraph.enableReferenceAxisFrame]; + self.leftFrameButton.on = self.myGraph.enableLeftReferenceAxisFrameLine; + self.rightFrameButton.on = self.myGraph.enableRightReferenceAxisFrameLine; + self.topFrameButton.on = self.myGraph.enableTopReferenceAxisFrameLine; + self.bottomFrameButton.on = self.myGraph.enableBottomReferenceAxisFrameLine; + + self.displayDotsSwitch.on = self.myGraph.alwaysDisplayDots; + self.displayDotsOnlySwitch.on = self.myGraph.displayDotsOnly; + self.sizePointField.floatValue = self.myGraph.sizePoint; + self.popupReportSwitch.on = self.myGraph.enablePopUpReport; + self.displayLabelsSwitch.on = self.myGraph.alwaysDisplayPopUpLabels; + self.testDisplayPopupCallBack.on = self.detailViewController.testAlwaysDisplayPopup; + self.labelTextFormat.text = self.detailViewController.popUpText; + self.poupLabelSuffix.text = self.detailViewController.popUpSuffix; + self.popupLabelPrefix.text = self.detailViewController.popUpPrefix; + self.enableCustomViewSwitch.on = self.detailViewController.provideCustomView; + self.enableNoDataLabelSwitch.on = self.detailViewController.noDataLabel; + self.noDataLabelTextField.text = self.detailViewController.noDataText; + + [self updateAnimationGraphStyle]; + self.animationEntranceTime.floatValue = self.myGraph.animationGraphEntranceTime; + self.dotsWhileAnimateSwitch.on = self.myGraph.displayDotsWhileAnimating; + self.touchReportSwitch.on = self.myGraph.enableTouchReport; + self.widthTouchInputLineField.floatValue = self.myGraph.widthTouchInputLine; + + // self.fontNameButton = self.myGraph.xx; + // self.fontSizeField = self.myGraph.xx; + self.numberFormatField.text = self.myGraph.formatStringForValues; + + + +} + +/* properties/methods not implemented: + touchReportFingersRequired, + autoScaleYAxis + + Colors/Gradients + + averageLine: Color/alpha/dashPashpattern + Top: Color/Alpha/Gradient + Line: Color/alpha/gradient/gradientLineDirection + ReferenceLines + Point: color + touchInputLine: color/alopha + XAxis: color/colorBackground, alphaBackground, lineDashPattern + Yaxis: color/colorBackground, alphaBackground, lineDashPattern + noDataLabel: color/Font + */ + + +#pragma mark Main Line +- (IBAction)widthLineDidChange:(UITextField *)sender { + float value = sender.text.floatValue; + if (value > 0.0f) { + self.myGraph.widthLine = sender.text.doubleValue; + } + [self.myGraph reloadGraph]; +} + +- (IBAction)staticPaddingDidChange:(UITextField *)sender { + self.detailViewController.staticPaddingValue = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableBezier:(UISwitch *)sender { + self.myGraph.enableBezierCurve = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)interpolateNullValues:(UISwitch *)sender { + self.myGraph.interpolateNullValues = sender.on; + [self.myGraph reloadGraph]; +} + +#pragma mark Axes and Reference Lines + +- (IBAction)enableXAxisLabel:(UISwitch *)sender { + self.myGraph.enableXAxisLabel = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)numberOfGapsBetweenLabelDidChange:(UITextField *)sender { + self.detailViewController.numberOfGapsBetweenLabels = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)baseIndexForXAxisDidChange:(UITextField *)sender { + self.detailViewController.baseIndexForXAxis = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} +- (IBAction)incrementIndexForXAxisDidChange:(UITextField *)sender { + self.detailViewController.incrementIndexForXAxis = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} +- (IBAction)enableArrayOfIndicesForXAxis:(UISwitch *)sender { + self.detailViewController.provideIncrementPositionsForXAxis = sender.on; + [self.myGraph reloadGraph]; + +} + +#pragma mark Axes and Reference Lines + +- (IBAction)enableYAxisLabel:(UISwitch *)sender { + self.myGraph.enableYAxisLabel = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)positionYAxisRight:(UISwitch *)sender { + self.myGraph.positionYAxisRight = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)minValueDidChange:(UITextField *)sender { + self.detailViewController.minValue = sender.text.doubleValue; + [self.myGraph reloadGraph]; + +} + +- (IBAction)maxValueDidChange:(UITextField *)sender { + self.detailViewController.maxValue = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)numberofYAxisDidChange:(UITextField *)sender { + if (sender.text.integerValue <= 0) { + sender.text = @"1.0"; + } + self.detailViewController.numberOfYAxisLabels = sender.text.integerValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)yAxisPrefixDidChange:(UITextField *)sender { + self.detailViewController.yAxisPrefix = sender.text; + [self.myGraph reloadGraph]; +} + +- (IBAction)yAxisSuffixDidChange:(UITextField *)sender { + self.detailViewController.yAxisSuffix = sender.text; + [self.myGraph reloadGraph]; +} +- (IBAction)baseValueForYAxisDidChange:(UITextField *)sender { + self.detailViewController.baseValueForYAxis = sender.text.doubleValue; + [self.myGraph reloadGraph]; + +} +- (IBAction)incrementValueForYAxisDidChange:(UITextField *)sender { + self.detailViewController.incrementValueForYAxis = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +#pragma mark Average Line +- (IBAction)enableAverageLine:(UISwitch *)sender { + self.myGraph.averageLine.enableAverageLine = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)averageLineTitleDidChange:(UITextField *)sender { + self.myGraph.averageLine.title = sender.text; + [self.myGraph reloadGraph]; +} + +- (IBAction)averageLineWidthDidChange:(UITextField *)sender { + if (sender.text.floatValue <= 0) { + sender.text = @"1.0"; + } + self.myGraph.averageLine.width = sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +#pragma mark Reference Lines + +- (IBAction)widthReferenceLines:(UITextField *)sender { + if (sender.text.floatValue <= 0) { + sender.text = @"1.0"; + } + self.myGraph.widthReferenceLines = (CGFloat) sender.text.doubleValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableReferenceXAxisLines:(UISwitch *)sender { + self.myGraph.enableReferenceXAxisLines = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableReferenceYAxisLines:(UISwitch *)sender { + self.myGraph.enableReferenceYAxisLines = sender.on; + [self.myGraph reloadGraph]; +} + +-(void) updateReferenceAxisFrame: (BOOL) newState { + self.myGraph.enableReferenceAxisFrame = newState; + self.frameReferenceAxesCell.alpha = newState ? 1.0 : 0.5 ; + self.frameReferenceAxesCell.userInteractionEnabled = newState; +} + +- (IBAction)enableReferenceAxisFrame:(UISwitch *)sender { + [self updateReferenceAxisFrame:sender.on]; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableLeftReferenceAxisFrameLine:(UIButton *)button { + BOOL newState = button.on; + self.myGraph.enableLeftReferenceAxisFrameLine = newState; + button.on = newState; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableRightReferenceAxisFrameLine:(UIButton *)button { + BOOL newState = button.on; + self.myGraph.enableRightReferenceAxisFrameLine = newState; + button.on = newState; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableTopReferenceAxisFrameLine:(UIButton *)button { + BOOL newState = button.on; + self.myGraph.enableTopReferenceAxisFrameLine = newState; + button.on = newState; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableBottomReferenceAxisFrameLine:(UIButton *)button { + BOOL newState = button.on; + self.myGraph.enableBottomReferenceAxisFrameLine = newState; + button.on = newState; + [self.myGraph reloadGraph]; +} + +#pragma mark Dots & Labels section + +- (IBAction)alwaysDisplayDots:(UISwitch *)sender { + self.myGraph.alwaysDisplayDots = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)displayDotsOnly:(UISwitch *)sender { + self.myGraph.displayDotsOnly = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)sizePointDidChange:(UITextField *)sender { + if (sender.text.floatValue <= 0) { + sender.text = @"1.0"; + } + self.myGraph.sizePoint = (CGFloat) sender.text.floatValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)enablePopUpReport:(UISwitch *)sender { + self.myGraph.enablePopUpReport = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)alwaysDisplayPopUpLabels:(UISwitch *)sender { + self.myGraph.alwaysDisplayPopUpLabels = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableTestDisplayPopups:(UISwitch *)sender { + self.detailViewController.testAlwaysDisplayPopup = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)labelTextDidChange:(UITextField *)sender { + self.detailViewController.popUpText = sender.text; + [self.myGraph reloadGraph]; +} + +- (IBAction)labelPrefixDidChange:(UITextField *)sender { + self.detailViewController.popUpPrefix = sender.text; + [self.myGraph reloadGraph]; +} + +- (IBAction)labelSuffixDidChange:(UITextField *)sender { + self.detailViewController.popUpSuffix = sender.text; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableCustomView:(UISwitch *)sender { + self.detailViewController.provideCustomView = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableNoDataLabel:(UISwitch *)sender { + self.detailViewController.noDataLabel = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)noDataLabelTextDidChange:(UITextField *)sender { + self.detailViewController.noDataText = sender.text; + [self.myGraph reloadGraph]; +} + +#pragma mark Animation section +// +//typedef NS_ENUM(NSInteger, BEMLineAnimation) { +// /// The draw animation draws the lines from left to right and bottom to top. +// BEMLineAnimationDraw, +// /// The fade animation fades in the lines from 0% opaque to 100% opaque (based on the \p lineAlpha property). +// BEMLineAnimationFade, +// /// The expand animation expands the lines from a small point to their full width (based on the \p lineWidth property). +// BEMLineAnimationExpand, +// /// No animation is used to display the graph +// BEMLineAnimationNone +//}; +// +-(void) updateAnimationGraphStyle { + NSString * newTitle = @""; + switch (self.myGraph.animationGraphStyle) { + case BEMLineAnimationDraw: + newTitle = @"Draw"; + break; + case BEMLineAnimationFade: + newTitle = @"Fade"; + break; + case BEMLineAnimationExpand: + newTitle = @"Expand"; + break; + case BEMLineAnimationNone: + newTitle = @"None"; + break; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcovered-switch-default" + default: + newTitle = @"N/A"; + break; +#pragma clang diagnostic pop + } + [self.animationGraphStyleButton setTitle:newTitle forState:UIControlStateNormal]; +} + +- (IBAction)animationGraphStyle:(UIButton *)sender { + if (self.myGraph.animationGraphStyle == BEMLineAnimationNone) { + self.myGraph.animationGraphStyle = BEMLineAnimationDraw; + } else { + self.myGraph.animationGraphStyle ++; + } + [self updateAnimationGraphStyle]; + [self.myGraph reloadGraph]; +} + +- (IBAction)animationGraphEntranceTimeDidChange:(UITextField *)sender { + self.myGraph.animationGraphEntranceTime = (CGFloat) sender.text.floatValue; + [self.myGraph reloadGraph]; +} + +- (IBAction)displayDotsWhileAnimating:(UISwitch *)sender { + self.myGraph.displayDotsWhileAnimating = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)enableTouchReport:(UISwitch *)sender { + self.myGraph.enableTouchReport = sender.on; + [self.myGraph reloadGraph]; +} + +- (IBAction)widthTouchInputLineDidChange:(UITextField *)sender { + if (sender.text.floatValue <= 0) { + sender.text = @"1.0"; + } + self.myGraph.widthTouchInputLine = (CGFloat) sender.text.floatValue; + [self.myGraph reloadGraph]; +} + + +#pragma mark TextFormatting + +- (IBAction)fontFamily:(UIButton *)sender { + // done in IB: [self performSegueWithIdentifier:@"FontPicker" sender:self]; +} + +-(void) updateFont: (NSString *) fontName { + if (!fontName) fontName = self.fontNameButton.titleLabel.text; + CGFloat fontSize = (CGFloat)self.fontSizeField.text.floatValue; + if (fontSize < 1.0) fontSize = 14.0; + UIFont * newFont = nil; + if ([@"System" isEqualToString:fontName]) { + newFont = [UIFont systemFontOfSize:fontSize]; + } else { + newFont = [UIFont fontWithName:fontName size:fontSize]; + } + if (newFont) { + self.myGraph.labelFont = newFont; + self.fontNameButton.titleLabel.font = newFont; + [self.myGraph reloadGraph]; + } +} + +- (void)fontPickerViewController:(ARFontPickerViewController *)fontPicker didSelectFont:(NSString *)fontName { + [fontPicker dismissViewControllerAnimated:YES completion:nil]; + self.fontNameButton.enabled = NO; + [self.fontNameButton setTitle:fontName forState:UIControlStateNormal]; + self.fontNameButton.enabled = YES; + [self updateFont: fontName]; +} + +- (IBAction)fontSizeFieldChanged:(UITextField *)sender { + [self updateFont:nil]; + +} + +- (IBAction)numberFormatChanged:(UITextField *)sender { + NSString * newFormat = sender.text ?: @""; + self.myGraph.formatStringForValues = newFormat; + [self.myGraph reloadGraph]; +} + +#pragma mark - Segues + +- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { + if ([[segue identifier] isEqualToString:@"showDetail"]) { + DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController]; + controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + controller.navigationItem.leftItemsSupplementBackButton = YES; + } else if ([[segue identifier] isEqualToString:@"FontPicker"]) { + ARFontPickerViewController * controller = (ARFontPickerViewController*) [segue destinationViewController]; + controller.delegate = self; + } +} + +#pragma mark - Table View + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + //static sections + return [super numberOfSectionsInTableView:tableView]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + //static cells + return [super tableView:tableView numberOfRowsInSection:section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + //static cells + return [super tableView:tableView cellForRowAtIndexPath:indexPath]; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { + // Return NO if you do not want the specified item to be editable. + return NO; +} + +#pragma mark TextDelegate + +-(BOOL) textFieldShouldReturn:(UITextField *)textField { + [textField resignFirstResponder]; + return YES; +} + + + +@end + diff --git a/Sample Project/TestBed/StatsViewController.h b/Sample Project/TestBed/StatsViewController.h new file mode 100644 index 0000000..2615045 --- /dev/null +++ b/Sample Project/TestBed/StatsViewController.h @@ -0,0 +1,29 @@ +// +// StatsViewController.h +// SimpleLineChart +// +// Created by iRare Media on 1/6/14. +// Copyright (c) 2014 Boris Emorine. All rights reserved. +// Copyright (c) 2014 Sam Spencer. +// +@import UIKit; + +@interface StatsViewController : UITableViewController + +@property (weak, nonatomic) IBOutlet UILabel *standardDeviationLabel; +@property (weak, nonatomic) IBOutlet UILabel *averageLabel; +@property (weak, nonatomic) IBOutlet UILabel *medianLabel; +@property (weak, nonatomic) IBOutlet UILabel *modeLabel; +@property (weak, nonatomic) IBOutlet UILabel *minimumLabel; +@property (weak, nonatomic) IBOutlet UILabel *maximumLabel; +@property (weak, nonatomic) IBOutlet UIImageView *snapshotImageView; + +@property (strong, nonatomic) NSString *standardDeviation; +@property (strong, nonatomic) NSString *average; +@property (strong, nonatomic) NSString *median; +@property (strong, nonatomic) NSString *mode; +@property (strong, nonatomic) NSString *minimum; +@property (strong, nonatomic) NSString *maximum; +@property (strong, nonatomic) UIImage *snapshotImage; + +@end diff --git a/Sample Project/TestBed/StatsViewController.m b/Sample Project/TestBed/StatsViewController.m new file mode 100644 index 0000000..b387c6d --- /dev/null +++ b/Sample Project/TestBed/StatsViewController.m @@ -0,0 +1,86 @@ +// +// StatsViewController.m +// SimpleLineChart +// +// Created by iRare Media on 1/6/14. +// Copyright (c) 2014 Boris Emorine. All rights reserved. +// Copyright (c) 2014 Sam Spencer. +// + +#import "StatsViewController.h" + +@interface StatsViewController () + +@end + +@implementation StatsViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view. +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + self.standardDeviationLabel.text = self.standardDeviation; + self.averageLabel.text = self.average; + self.medianLabel.text = self.median; + self. modeLabel.text = self.mode; + self.maximumLabel.text = self.maximum; + self.minimumLabel.text = self.minimum; + self.snapshotImageView.image = self.snapshotImage; +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return (section == 0) ? 6 : 1; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *CellIdentifier = @"Cell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; + if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; + + if (indexPath.row == 0 && indexPath.section == 0) { + cell.textLabel.text = self.standardDeviation; + cell.detailTextLabel.text = @"Standard Deviation"; + return cell; + } else if (indexPath.row == 1) { + cell.textLabel.text = self.average; + cell.detailTextLabel.text = @"Average"; + return cell; + } else if (indexPath.row == 2) { + cell.textLabel.text = self.median; + cell.detailTextLabel.text = @"Median"; + return cell; + } else if (indexPath.row == 3) { + cell.textLabel.text = self.mode; + cell.detailTextLabel.text = @"Mode"; + return cell; + } else if (indexPath.row == 4) { + cell.textLabel.text = self.maximum; + cell.detailTextLabel.text = @"Maximum Value"; + return cell; + } else if (indexPath.row == 5) { + cell.textLabel.text = self.minimum; + cell.detailTextLabel.text = @"Minimum Value"; + return cell; + } else if (indexPath.row == 0 && indexPath.section == 1) { + cell.textLabel.text = @"Rendered Snapshot"; + cell.imageView.image = self.snapshotImage; + return cell; + } else { + NSLog(@"Unknown"); + return cell; + } +} + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [self.tableView deselectRowAtIndexPath:indexPath animated:YES]; +} + +@end diff --git a/Sample Project/TestBed/main.m b/Sample Project/TestBed/main.m new file mode 100644 index 0000000..4b8e19f --- /dev/null +++ b/Sample Project/TestBed/main.m @@ -0,0 +1,16 @@ +// +// main.m +// TestBed +// +// Created by Hugh Mackworth on 3/18/17. +// Copyright © 2017 Boris Emorine. All rights reserved. +// + +@import UIKit; +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} From 7dc9f3a4d268517b937dc6cf77cb0e0f800be208 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sat, 25 Mar 2017 17:47:44 -0700 Subject: [PATCH 04/15] Add colors, gradients, and alphas to TestBed Fix many bugs and buglets --- .../SimpleLineChart.xcodeproj/project.pbxproj | 1569 +++++++++-------- .../TestBed/Base.lproj/Main.storyboard | 995 +++++++++-- Sample Project/TestBed/DetailViewController.m | 71 +- .../MSColorPicker/MSColorComponentView.h | 69 + .../MSColorPicker/MSColorComponentView.m | 222 +++ .../TestBed/MSColorPicker/MSColorPicker.h | 29 + .../MSColorPicker/MSColorSelectionView.h | 63 + .../MSColorPicker/MSColorSelectionView.m | 126 ++ .../MSColorSelectionViewController.h | 64 + .../MSColorSelectionViewController.m | 92 + .../TestBed/MSColorPicker/MSColorUtils.h | 96 + .../TestBed/MSColorPicker/MSColorUtils.m | 177 ++ .../TestBed/MSColorPicker/MSColorView.h | 65 + .../TestBed/MSColorPicker/MSColorWheelView.h | 45 + .../TestBed/MSColorPicker/MSColorWheelView.m | 240 +++ .../TestBed/MSColorPicker/MSHSBView.h | 36 + .../TestBed/MSColorPicker/MSHSBView.m | 229 +++ .../TestBed/MSColorPicker/MSRGBView.h | 36 + .../TestBed/MSColorPicker/MSRGBView.m | 225 +++ .../TestBed/MSColorPicker/MSSliderView.h | 54 + .../TestBed/MSColorPicker/MSSliderView.m | 187 ++ .../TestBed/MSColorPicker/MSThumbView.h | 14 + .../TestBed/MSColorPicker/MSThumbView.m | 56 + .../UIControl+HitTestEdgeInsets.h | 37 + .../UIControl+HitTestEdgeInsets.m | 65 + Sample Project/TestBed/MasterViewController.m | 654 +++---- 26 files changed, 4242 insertions(+), 1274 deletions(-) create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorComponentView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorComponentView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorPicker.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorSelectionView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorSelectionView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorUtils.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorUtils.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorWheelView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSColorWheelView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSHSBView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSHSBView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSRGBView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSRGBView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSSliderView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSSliderView.m create mode 100644 Sample Project/TestBed/MSColorPicker/MSThumbView.h create mode 100644 Sample Project/TestBed/MSColorPicker/MSThumbView.m create mode 100644 Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.h create mode 100644 Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.m diff --git a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj index c6efb90..bd6f6d1 100644 --- a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj +++ b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj @@ -1,790 +1,863 @@ // !$*UTF8*$! { -archiveVersion = 1; -classes = { -}; -objectVersion = 46; -objects = { + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { /* Begin PBXBuildFile section */ -1E960A851E7DF942000E2BB8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A841E7DF942000E2BB8 /* main.m */; }; -1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A871E7DF942000E2BB8 /* AppDelegate.m */; }; -1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A8C1E7DF942000E2BB8 /* Main.storyboard */; }; -1E960A901E7DF942000E2BB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */; }; -1E960A931E7DF942000E2BB8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */; }; -1E960AA01E7DF9BC000E2BB8 /* ARFontPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */; }; -1E960AA11E7DF9BC000E2BB8 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */; }; -1E960AA21E7DF9BC000E2BB8 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */; }; -1E960AA31E7DF9BC000E2BB8 /* StatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */; }; -1E960AA41E7E094D000E2BB8 /* BEMSimpleLineGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */; }; -1E960AA51E7E097C000E2BB8 /* BEMCircle.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A59187D15F7003E407D /* BEMCircle.m */; }; -1E960AA61E7E097C000E2BB8 /* BEMLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5B187D15F7003E407D /* BEMLine.m */; }; -1E960AA71E7E097C000E2BB8 /* BEMAverageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = A63990B41AD4923900B14D88 /* BEMAverageLine.m */; }; -1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */; }; -1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */; }; -99B15643187B412400B24591 /* StatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 99B15642187B412400B24591 /* StatsViewController.m */; }; -99B3FA3A1877898B00539A7B /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 99B3FA381877898B00539A7B /* LICENSE */; }; -99B3FA3B1877898B00539A7B /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 99B3FA391877898B00539A7B /* README.md */; }; -A63990B51AD4923900B14D88 /* BEMAverageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = A63990B41AD4923900B14D88 /* BEMAverageLine.m */; }; -A64594521BAB257B00D6B8FD /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */; }; -A6AC895B1C5882DD0052AB1C /* BEMGraphCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */; }; -C3B90A5F187D15F7003E407D /* BEMCircle.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A59187D15F7003E407D /* BEMCircle.m */; }; -C3B90A60187D15F7003E407D /* BEMLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5B187D15F7003E407D /* BEMLine.m */; }; -C3B90A61187D15F7003E407D /* BEMSimpleLineGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */; }; -C3BCA7E71B8ECCA6007E6090 /* CustomizationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */; }; -C3DC80671903845D0080FF06 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */; }; -C3FD8159186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */; }; -C3FD815B186DFD9A00FD8ED3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */; }; -C3FD815D186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */; }; -C3FD8163186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */; }; -C3FD8165186DFD9A00FD8ED3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8164186DFD9A00FD8ED3 /* main.m */; }; -C3FD8169186DFD9A00FD8ED3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */; }; -C3FD816F186DFD9A00FD8ED3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD816E186DFD9A00FD8ED3 /* ViewController.m */; }; -C3FD8171186DFD9A00FD8ED3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */; }; -C3FD8178186DFD9A00FD8ED3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */; }; -C3FD8179186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */; }; -C3FD817A186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */; }; -C3FD8182186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */; }; -C3FD8184186DFD9A00FD8ED3 /* SimpleLineChartTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */; }; + 1E960A851E7DF942000E2BB8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A841E7DF942000E2BB8 /* main.m */; }; + 1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A871E7DF942000E2BB8 /* AppDelegate.m */; }; + 1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A8C1E7DF942000E2BB8 /* Main.storyboard */; }; + 1E960A901E7DF942000E2BB8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */; }; + 1E960A931E7DF942000E2BB8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */; }; + 1E960AA01E7DF9BC000E2BB8 /* ARFontPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */; }; + 1E960AA11E7DF9BC000E2BB8 /* DetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */; }; + 1E960AA21E7DF9BC000E2BB8 /* MasterViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */; }; + 1E960AA31E7DF9BC000E2BB8 /* StatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */; }; + 1E960AA41E7E094D000E2BB8 /* BEMSimpleLineGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */; }; + 1E960AA51E7E097C000E2BB8 /* BEMCircle.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A59187D15F7003E407D /* BEMCircle.m */; }; + 1E960AA61E7E097C000E2BB8 /* BEMLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5B187D15F7003E407D /* BEMLine.m */; }; + 1E960AA71E7E097C000E2BB8 /* BEMAverageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = A63990B41AD4923900B14D88 /* BEMAverageLine.m */; }; + 1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */; }; + 1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */; }; + 1E960B121E7F9C80000E2BB8 /* MSColorComponentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */; }; + 1E960B131E7F9C80000E2BB8 /* MSColorSelectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B001E7F9C80000E2BB8 /* MSColorSelectionView.m */; }; + 1E960B141E7F9C80000E2BB8 /* MSColorSelectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B021E7F9C80000E2BB8 /* MSColorSelectionViewController.m */; }; + 1E960B151E7F9C80000E2BB8 /* MSColorUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B041E7F9C80000E2BB8 /* MSColorUtils.m */; }; + 1E960B161E7F9C80000E2BB8 /* MSColorWheelView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B071E7F9C80000E2BB8 /* MSColorWheelView.m */; }; + 1E960B171E7F9C80000E2BB8 /* MSHSBView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B091E7F9C80000E2BB8 /* MSHSBView.m */; }; + 1E960B181E7F9C80000E2BB8 /* MSRGBView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B0B1E7F9C80000E2BB8 /* MSRGBView.m */; }; + 1E960B191E7F9C80000E2BB8 /* MSSliderView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B0D1E7F9C80000E2BB8 /* MSSliderView.m */; }; + 1E960B1A1E7F9C80000E2BB8 /* MSThumbView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B0F1E7F9C80000E2BB8 /* MSThumbView.m */; }; + 1E960B1B1E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B111E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.m */; }; + 99B15643187B412400B24591 /* StatsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 99B15642187B412400B24591 /* StatsViewController.m */; }; + 99B3FA3A1877898B00539A7B /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 99B3FA381877898B00539A7B /* LICENSE */; }; + 99B3FA3B1877898B00539A7B /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 99B3FA391877898B00539A7B /* README.md */; }; + A63990B51AD4923900B14D88 /* BEMAverageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = A63990B41AD4923900B14D88 /* BEMAverageLine.m */; }; + A64594521BAB257B00D6B8FD /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */; }; + A6AC895B1C5882DD0052AB1C /* BEMGraphCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */; }; + C3B90A5F187D15F7003E407D /* BEMCircle.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A59187D15F7003E407D /* BEMCircle.m */; }; + C3B90A60187D15F7003E407D /* BEMLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5B187D15F7003E407D /* BEMLine.m */; }; + C3B90A61187D15F7003E407D /* BEMSimpleLineGraphView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */; }; + C3BCA7E71B8ECCA6007E6090 /* CustomizationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */; }; + C3DC80671903845D0080FF06 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */; }; + C3FD8159186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */; }; + C3FD815B186DFD9A00FD8ED3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */; }; + C3FD815D186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */; }; + C3FD8163186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */; }; + C3FD8165186DFD9A00FD8ED3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8164186DFD9A00FD8ED3 /* main.m */; }; + C3FD8169186DFD9A00FD8ED3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */; }; + C3FD816F186DFD9A00FD8ED3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD816E186DFD9A00FD8ED3 /* ViewController.m */; }; + C3FD8171186DFD9A00FD8ED3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */; }; + C3FD8178186DFD9A00FD8ED3 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */; }; + C3FD8179186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */; }; + C3FD817A186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */; }; + C3FD8182186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */; }; + C3FD8184186DFD9A00FD8ED3 /* SimpleLineChartTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ -C3FD817B186DFD9A00FD8ED3 /* PBXContainerItemProxy */ = { -isa = PBXContainerItemProxy; -containerPortal = C3FD814D186DFD9A00FD8ED3 /* Project object */; -proxyType = 1; -remoteGlobalIDString = C3FD8154186DFD9A00FD8ED3; -remoteInfo = SimpleLineChart; -}; + C3FD817B186DFD9A00FD8ED3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C3FD814D186DFD9A00FD8ED3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C3FD8154186DFD9A00FD8ED3; + remoteInfo = SimpleLineChart; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ -1E960A811E7DF942000E2BB8 /* TestBed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestBed.app; sourceTree = BUILT_PRODUCTS_DIR; }; -1E960A841E7DF942000E2BB8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; -1E960A861E7DF942000E2BB8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; -1E960A871E7DF942000E2BB8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; -1E960A8D1E7DF942000E2BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; -1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; -1E960A921E7DF942000E2BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; -1E960A941E7DF942000E2BB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -1E960A981E7DF9BB000E2BB8 /* ARFontPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARFontPickerViewController.h; sourceTree = ""; }; -1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARFontPickerViewController.m; sourceTree = ""; }; -1E960A9A1E7DF9BB000E2BB8 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; -1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; -1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; -1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; -1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; -1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; -1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon60x60@2x.png"; sourceTree = ""; }; -99B15641187B412400B24591 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; -99B15642187B412400B24591 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; -99B3FA381877898B00539A7B /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; -99B3FA391877898B00539A7B /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../README.md; sourceTree = ""; }; -A63990B31AD4923900B14D88 /* BEMAverageLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMAverageLine.h; sourceTree = ""; }; -A63990B41AD4923900B14D88 /* BEMAverageLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMAverageLine.m; sourceTree = ""; }; -A64594511BAB257B00D6B8FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Launch Screen.storyboard"; sourceTree = ""; }; -A6AC89591C5882DD0052AB1C /* BEMGraphCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMGraphCalculator.h; sourceTree = ""; }; -A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMGraphCalculator.m; sourceTree = ""; }; -C3B90A58187D15F7003E407D /* BEMCircle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMCircle.h; sourceTree = ""; }; -C3B90A59187D15F7003E407D /* BEMCircle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMCircle.m; sourceTree = ""; }; -C3B90A5A187D15F7003E407D /* BEMLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMLine.h; sourceTree = ""; }; -C3B90A5B187D15F7003E407D /* BEMLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMLine.m; sourceTree = ""; }; -C3B90A5C187D15F7003E407D /* BEMSimpleLineGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMSimpleLineGraphView.h; sourceTree = ""; }; -C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMSimpleLineGraphView.m; sourceTree = ""; }; -C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomizationTests.m; sourceTree = ""; }; -C3BCA7E81B8ECE4E007E6090 /* contantsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = contantsTests.h; sourceTree = ""; }; -C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleLineChart.app; sourceTree = BUILT_PRODUCTS_DIR; }; -C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; -C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; -C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; -C3FD8160186DFD9A00FD8ED3 /* SimpleLineChart-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleLineChart-Info.plist"; sourceTree = ""; }; -C3FD8162186DFD9A00FD8ED3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; -C3FD8164186DFD9A00FD8ED3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; -C3FD8166186DFD9A00FD8ED3 /* SimpleLineChart-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleLineChart-Prefix.pch"; sourceTree = ""; }; -C3FD8167186DFD9A00FD8ED3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; -C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; -C3FD816B186DFD9A00FD8ED3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; -C3FD816D186DFD9A00FD8ED3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; -C3FD816E186DFD9A00FD8ED3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; -C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; -C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleLineChartTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; -C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; -C3FD817F186DFD9A00FD8ED3 /* SimpleLineChartTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleLineChartTests-Info.plist"; sourceTree = ""; }; -C3FD8181186DFD9A00FD8ED3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; -C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimpleLineChartTests.m; sourceTree = ""; }; + 1E960A811E7DF942000E2BB8 /* TestBed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestBed.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E960A841E7DF942000E2BB8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 1E960A861E7DF942000E2BB8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 1E960A871E7DF942000E2BB8 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 1E960A8D1E7DF942000E2BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 1E960A921E7DF942000E2BB8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 1E960A941E7DF942000E2BB8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 1E960A981E7DF9BB000E2BB8 /* ARFontPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARFontPickerViewController.h; sourceTree = ""; }; + 1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARFontPickerViewController.m; sourceTree = ""; }; + 1E960A9A1E7DF9BB000E2BB8 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; + 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; + 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; + 1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; + 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; + 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon60x60@2x.png"; sourceTree = ""; }; + 1E960AFC1E7F9C80000E2BB8 /* MSColorComponentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorComponentView.h; sourceTree = ""; }; + 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorComponentView.m; sourceTree = ""; }; + 1E960AFE1E7F9C80000E2BB8 /* MSColorPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorPicker.h; sourceTree = ""; }; + 1E960AFF1E7F9C80000E2BB8 /* MSColorSelectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorSelectionView.h; sourceTree = ""; }; + 1E960B001E7F9C80000E2BB8 /* MSColorSelectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorSelectionView.m; sourceTree = ""; }; + 1E960B011E7F9C80000E2BB8 /* MSColorSelectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorSelectionViewController.h; sourceTree = ""; }; + 1E960B021E7F9C80000E2BB8 /* MSColorSelectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorSelectionViewController.m; sourceTree = ""; }; + 1E960B031E7F9C80000E2BB8 /* MSColorUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorUtils.h; sourceTree = ""; }; + 1E960B041E7F9C80000E2BB8 /* MSColorUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorUtils.m; sourceTree = ""; }; + 1E960B051E7F9C80000E2BB8 /* MSColorView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorView.h; sourceTree = ""; }; + 1E960B061E7F9C80000E2BB8 /* MSColorWheelView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorWheelView.h; sourceTree = ""; }; + 1E960B071E7F9C80000E2BB8 /* MSColorWheelView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorWheelView.m; sourceTree = ""; }; + 1E960B081E7F9C80000E2BB8 /* MSHSBView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSHSBView.h; sourceTree = ""; }; + 1E960B091E7F9C80000E2BB8 /* MSHSBView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHSBView.m; sourceTree = ""; }; + 1E960B0A1E7F9C80000E2BB8 /* MSRGBView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSRGBView.h; sourceTree = ""; }; + 1E960B0B1E7F9C80000E2BB8 /* MSRGBView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSRGBView.m; sourceTree = ""; }; + 1E960B0C1E7F9C80000E2BB8 /* MSSliderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSliderView.h; sourceTree = ""; }; + 1E960B0D1E7F9C80000E2BB8 /* MSSliderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSliderView.m; sourceTree = ""; }; + 1E960B0E1E7F9C80000E2BB8 /* MSThumbView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSThumbView.h; sourceTree = ""; }; + 1E960B0F1E7F9C80000E2BB8 /* MSThumbView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSThumbView.m; sourceTree = ""; }; + 1E960B101E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIControl+HitTestEdgeInsets.h"; sourceTree = ""; }; + 1E960B111E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIControl+HitTestEdgeInsets.m"; sourceTree = ""; }; + 99B15641187B412400B24591 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; + 99B15642187B412400B24591 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; + 99B3FA381877898B00539A7B /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; + 99B3FA391877898B00539A7B /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../README.md; sourceTree = ""; }; + A63990B31AD4923900B14D88 /* BEMAverageLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMAverageLine.h; sourceTree = ""; }; + A63990B41AD4923900B14D88 /* BEMAverageLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMAverageLine.m; sourceTree = ""; }; + A64594511BAB257B00D6B8FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Launch Screen.storyboard"; sourceTree = ""; }; + A6AC89591C5882DD0052AB1C /* BEMGraphCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMGraphCalculator.h; sourceTree = ""; }; + A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMGraphCalculator.m; sourceTree = ""; }; + C3B90A58187D15F7003E407D /* BEMCircle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMCircle.h; sourceTree = ""; }; + C3B90A59187D15F7003E407D /* BEMCircle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMCircle.m; sourceTree = ""; }; + C3B90A5A187D15F7003E407D /* BEMLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMLine.h; sourceTree = ""; }; + C3B90A5B187D15F7003E407D /* BEMLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMLine.m; sourceTree = ""; }; + C3B90A5C187D15F7003E407D /* BEMSimpleLineGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMSimpleLineGraphView.h; sourceTree = ""; }; + C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMSimpleLineGraphView.m; sourceTree = ""; }; + C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomizationTests.m; sourceTree = ""; }; + C3BCA7E81B8ECE4E007E6090 /* contantsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = contantsTests.h; sourceTree = ""; }; + C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleLineChart.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + C3FD8160186DFD9A00FD8ED3 /* SimpleLineChart-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleLineChart-Info.plist"; sourceTree = ""; }; + C3FD8162186DFD9A00FD8ED3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + C3FD8164186DFD9A00FD8ED3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + C3FD8166186DFD9A00FD8ED3 /* SimpleLineChart-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SimpleLineChart-Prefix.pch"; sourceTree = ""; }; + C3FD8167186DFD9A00FD8ED3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + C3FD816B186DFD9A00FD8ED3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + C3FD816D186DFD9A00FD8ED3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + C3FD816E186DFD9A00FD8ED3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; + C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SimpleLineChartTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + C3FD817F186DFD9A00FD8ED3 /* SimpleLineChartTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SimpleLineChartTests-Info.plist"; sourceTree = ""; }; + C3FD8181186DFD9A00FD8ED3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SimpleLineChartTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ -1E960A7E1E7DF942000E2BB8 /* Frameworks */ = { -isa = PBXFrameworksBuildPhase; -buildActionMask = 2147483647; -files = ( -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8152186DFD9A00FD8ED3 /* Frameworks */ = { -isa = PBXFrameworksBuildPhase; -buildActionMask = 2147483647; -files = ( -C3FD815B186DFD9A00FD8ED3 /* CoreGraphics.framework in Frameworks */, -C3FD815D186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */, -C3FD8159186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */, -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8173186DFD9A00FD8ED3 /* Frameworks */ = { -isa = PBXFrameworksBuildPhase; -buildActionMask = 2147483647; -files = ( -C3FD8178186DFD9A00FD8ED3 /* XCTest.framework in Frameworks */, -C3FD817A186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */, -C3FD8179186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */, -); -runOnlyForDeploymentPostprocessing = 0; -}; + 1E960A7E1E7DF942000E2BB8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8152186DFD9A00FD8ED3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C3FD815B186DFD9A00FD8ED3 /* CoreGraphics.framework in Frameworks */, + C3FD815D186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */, + C3FD8159186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8173186DFD9A00FD8ED3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C3FD8178186DFD9A00FD8ED3 /* XCTest.framework in Frameworks */, + C3FD817A186DFD9A00FD8ED3 /* UIKit.framework in Frameworks */, + C3FD8179186DFD9A00FD8ED3 /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ -1E960A821E7DF942000E2BB8 /* TestBed */ = { -isa = PBXGroup; -children = ( -1E960A861E7DF942000E2BB8 /* AppDelegate.h */, -1E960A871E7DF942000E2BB8 /* AppDelegate.m */, -1E960A8C1E7DF942000E2BB8 /* Main.storyboard */, -1E960A981E7DF9BB000E2BB8 /* ARFontPickerViewController.h */, -1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */, -1E960A9A1E7DF9BB000E2BB8 /* DetailViewController.h */, -1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */, -1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */, -1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */, -1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */, -1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */, -1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */, -1E960A831E7DF942000E2BB8 /* Supporting Files */, -); -path = TestBed; -sourceTree = ""; -}; -1E960A831E7DF942000E2BB8 /* Supporting Files */ = { -isa = PBXGroup; -children = ( -1E960A941E7DF942000E2BB8 /* Info.plist */, -1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */, -1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */, -1E960A841E7DF942000E2BB8 /* main.m */, -); -name = "Supporting Files"; -sourceTree = ""; -}; -C3B90A55187D15F7003E407D /* Classes */ = { -isa = PBXGroup; -children = ( -C3B90A5C187D15F7003E407D /* BEMSimpleLineGraphView.h */, -C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */, -C3B90A58187D15F7003E407D /* BEMCircle.h */, -C3B90A59187D15F7003E407D /* BEMCircle.m */, -C3B90A5A187D15F7003E407D /* BEMLine.h */, -C3B90A5B187D15F7003E407D /* BEMLine.m */, -A63990B41AD4923900B14D88 /* BEMAverageLine.m */, -A63990B31AD4923900B14D88 /* BEMAverageLine.h */, -A6AC89591C5882DD0052AB1C /* BEMGraphCalculator.h */, -A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */, -); -name = Classes; -path = ../Classes; -sourceTree = ""; -}; -C3FD814C186DFD9A00FD8ED3 = { -isa = PBXGroup; -children = ( -99B3FA381877898B00539A7B /* LICENSE */, -99B3FA391877898B00539A7B /* README.md */, -C3B90A55187D15F7003E407D /* Classes */, -C3FD815E186DFD9A00FD8ED3 /* SimpleLineChart */, -C3FD817D186DFD9A00FD8ED3 /* SimpleLineChartTests */, -1E960A821E7DF942000E2BB8 /* TestBed */, -C3FD8157186DFD9A00FD8ED3 /* Frameworks */, -C3FD8156186DFD9A00FD8ED3 /* Products */, -); -sourceTree = ""; -}; -C3FD8156186DFD9A00FD8ED3 /* Products */ = { -isa = PBXGroup; -children = ( -C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */, -C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */, -1E960A811E7DF942000E2BB8 /* TestBed.app */, -); -name = Products; -sourceTree = ""; -}; -C3FD8157186DFD9A00FD8ED3 /* Frameworks */ = { -isa = PBXGroup; -children = ( -C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */, -C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */, -C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */, -C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */, -); -name = Frameworks; -sourceTree = ""; -}; -C3FD815E186DFD9A00FD8ED3 /* SimpleLineChart */ = { -isa = PBXGroup; -children = ( -C3FD8167186DFD9A00FD8ED3 /* AppDelegate.h */, -C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */, -C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */, -C3FD816D186DFD9A00FD8ED3 /* ViewController.h */, -C3FD816E186DFD9A00FD8ED3 /* ViewController.m */, -99B15641187B412400B24591 /* StatsViewController.h */, -99B15642187B412400B24591 /* StatsViewController.m */, -C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */, -C3FD815F186DFD9A00FD8ED3 /* Supporting Files */, -); -path = SimpleLineChart; -sourceTree = ""; -}; -C3FD815F186DFD9A00FD8ED3 /* Supporting Files */ = { -isa = PBXGroup; -children = ( -A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */, -C3FD8160186DFD9A00FD8ED3 /* SimpleLineChart-Info.plist */, -C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */, -C3FD8164186DFD9A00FD8ED3 /* main.m */, -C3FD8166186DFD9A00FD8ED3 /* SimpleLineChart-Prefix.pch */, -); -name = "Supporting Files"; -sourceTree = ""; -}; -C3FD817D186DFD9A00FD8ED3 /* SimpleLineChartTests */ = { -isa = PBXGroup; -children = ( -C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */, -C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */, -C3BCA7E81B8ECE4E007E6090 /* contantsTests.h */, -C3FD817E186DFD9A00FD8ED3 /* Supporting Files */, -); -path = SimpleLineChartTests; -sourceTree = ""; -}; -C3FD817E186DFD9A00FD8ED3 /* Supporting Files */ = { -isa = PBXGroup; -children = ( -C3FD817F186DFD9A00FD8ED3 /* SimpleLineChartTests-Info.plist */, -C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */, -); -name = "Supporting Files"; -sourceTree = ""; -}; + 1E960A821E7DF942000E2BB8 /* TestBed */ = { + isa = PBXGroup; + children = ( + 1E960A861E7DF942000E2BB8 /* AppDelegate.h */, + 1E960A871E7DF942000E2BB8 /* AppDelegate.m */, + 1E960A8C1E7DF942000E2BB8 /* Main.storyboard */, + 1E960A981E7DF9BB000E2BB8 /* ARFontPickerViewController.h */, + 1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */, + 1E960A9A1E7DF9BB000E2BB8 /* DetailViewController.h */, + 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */, + 1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */, + 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */, + 1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */, + 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */, + 1E960AFB1E7F9C80000E2BB8 /* MSColorPicker */, + 1E960A8F1E7DF942000E2BB8 /* Assets.xcassets */, + 1E960A831E7DF942000E2BB8 /* Supporting Files */, + ); + path = TestBed; + sourceTree = ""; + }; + 1E960A831E7DF942000E2BB8 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 1E960A941E7DF942000E2BB8 /* Info.plist */, + 1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */, + 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */, + 1E960A841E7DF942000E2BB8 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1E960AFB1E7F9C80000E2BB8 /* MSColorPicker */ = { + isa = PBXGroup; + children = ( + 1E960AFC1E7F9C80000E2BB8 /* MSColorComponentView.h */, + 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */, + 1E960AFE1E7F9C80000E2BB8 /* MSColorPicker.h */, + 1E960AFF1E7F9C80000E2BB8 /* MSColorSelectionView.h */, + 1E960B001E7F9C80000E2BB8 /* MSColorSelectionView.m */, + 1E960B011E7F9C80000E2BB8 /* MSColorSelectionViewController.h */, + 1E960B021E7F9C80000E2BB8 /* MSColorSelectionViewController.m */, + 1E960B031E7F9C80000E2BB8 /* MSColorUtils.h */, + 1E960B041E7F9C80000E2BB8 /* MSColorUtils.m */, + 1E960B051E7F9C80000E2BB8 /* MSColorView.h */, + 1E960B061E7F9C80000E2BB8 /* MSColorWheelView.h */, + 1E960B071E7F9C80000E2BB8 /* MSColorWheelView.m */, + 1E960B081E7F9C80000E2BB8 /* MSHSBView.h */, + 1E960B091E7F9C80000E2BB8 /* MSHSBView.m */, + 1E960B0A1E7F9C80000E2BB8 /* MSRGBView.h */, + 1E960B0B1E7F9C80000E2BB8 /* MSRGBView.m */, + 1E960B0C1E7F9C80000E2BB8 /* MSSliderView.h */, + 1E960B0D1E7F9C80000E2BB8 /* MSSliderView.m */, + 1E960B0E1E7F9C80000E2BB8 /* MSThumbView.h */, + 1E960B0F1E7F9C80000E2BB8 /* MSThumbView.m */, + 1E960B101E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.h */, + 1E960B111E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.m */, + ); + path = MSColorPicker; + sourceTree = ""; + }; + C3B90A55187D15F7003E407D /* Classes */ = { + isa = PBXGroup; + children = ( + C3B90A5C187D15F7003E407D /* BEMSimpleLineGraphView.h */, + C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */, + C3B90A58187D15F7003E407D /* BEMCircle.h */, + C3B90A59187D15F7003E407D /* BEMCircle.m */, + C3B90A5A187D15F7003E407D /* BEMLine.h */, + C3B90A5B187D15F7003E407D /* BEMLine.m */, + A63990B41AD4923900B14D88 /* BEMAverageLine.m */, + A63990B31AD4923900B14D88 /* BEMAverageLine.h */, + A6AC89591C5882DD0052AB1C /* BEMGraphCalculator.h */, + A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */, + ); + name = Classes; + path = ../Classes; + sourceTree = ""; + }; + C3FD814C186DFD9A00FD8ED3 = { + isa = PBXGroup; + children = ( + 99B3FA381877898B00539A7B /* LICENSE */, + 99B3FA391877898B00539A7B /* README.md */, + C3B90A55187D15F7003E407D /* Classes */, + C3FD815E186DFD9A00FD8ED3 /* SimpleLineChart */, + C3FD817D186DFD9A00FD8ED3 /* SimpleLineChartTests */, + 1E960A821E7DF942000E2BB8 /* TestBed */, + C3FD8157186DFD9A00FD8ED3 /* Frameworks */, + C3FD8156186DFD9A00FD8ED3 /* Products */, + ); + sourceTree = ""; + }; + C3FD8156186DFD9A00FD8ED3 /* Products */ = { + isa = PBXGroup; + children = ( + C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */, + C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */, + 1E960A811E7DF942000E2BB8 /* TestBed.app */, + ); + name = Products; + sourceTree = ""; + }; + C3FD8157186DFD9A00FD8ED3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + C3FD8158186DFD9A00FD8ED3 /* Foundation.framework */, + C3FD815A186DFD9A00FD8ED3 /* CoreGraphics.framework */, + C3FD815C186DFD9A00FD8ED3 /* UIKit.framework */, + C3FD8177186DFD9A00FD8ED3 /* XCTest.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C3FD815E186DFD9A00FD8ED3 /* SimpleLineChart */ = { + isa = PBXGroup; + children = ( + C3FD8167186DFD9A00FD8ED3 /* AppDelegate.h */, + C3FD8168186DFD9A00FD8ED3 /* AppDelegate.m */, + C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */, + C3FD816D186DFD9A00FD8ED3 /* ViewController.h */, + C3FD816E186DFD9A00FD8ED3 /* ViewController.m */, + 99B15641187B412400B24591 /* StatsViewController.h */, + 99B15642187B412400B24591 /* StatsViewController.m */, + C3FD8170186DFD9A00FD8ED3 /* Images.xcassets */, + C3FD815F186DFD9A00FD8ED3 /* Supporting Files */, + ); + path = SimpleLineChart; + sourceTree = ""; + }; + C3FD815F186DFD9A00FD8ED3 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */, + C3FD8160186DFD9A00FD8ED3 /* SimpleLineChart-Info.plist */, + C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */, + C3FD8164186DFD9A00FD8ED3 /* main.m */, + C3FD8166186DFD9A00FD8ED3 /* SimpleLineChart-Prefix.pch */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C3FD817D186DFD9A00FD8ED3 /* SimpleLineChartTests */ = { + isa = PBXGroup; + children = ( + C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */, + C3FD8183186DFD9A00FD8ED3 /* SimpleLineChartTests.m */, + C3BCA7E81B8ECE4E007E6090 /* contantsTests.h */, + C3FD817E186DFD9A00FD8ED3 /* Supporting Files */, + ); + path = SimpleLineChartTests; + sourceTree = ""; + }; + C3FD817E186DFD9A00FD8ED3 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + C3FD817F186DFD9A00FD8ED3 /* SimpleLineChartTests-Info.plist */, + C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ -1E960A801E7DF942000E2BB8 /* TestBed */ = { -isa = PBXNativeTarget; -buildConfigurationList = 1E960A971E7DF942000E2BB8 /* Build configuration list for PBXNativeTarget "TestBed" */; -buildPhases = ( -1E960A7D1E7DF942000E2BB8 /* Sources */, -1E960A7E1E7DF942000E2BB8 /* Frameworks */, -1E960A7F1E7DF942000E2BB8 /* Resources */, -); -buildRules = ( -); -dependencies = ( -); -name = TestBed; -productName = TestBed; -productReference = 1E960A811E7DF942000E2BB8 /* TestBed.app */; -productType = "com.apple.product-type.application"; -}; -C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */ = { -isa = PBXNativeTarget; -buildConfigurationList = C3FD8187186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChart" */; -buildPhases = ( -C3FD8151186DFD9A00FD8ED3 /* Sources */, -C3FD8152186DFD9A00FD8ED3 /* Frameworks */, -C3FD8153186DFD9A00FD8ED3 /* Resources */, -); -buildRules = ( -); -dependencies = ( -); -name = SimpleLineChart; -productName = SimpleLineChart; -productReference = C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */; -productType = "com.apple.product-type.application"; -}; -C3FD8175186DFD9A00FD8ED3 /* SimpleLineChartTests */ = { -isa = PBXNativeTarget; -buildConfigurationList = C3FD818A186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChartTests" */; -buildPhases = ( -C3FD8172186DFD9A00FD8ED3 /* Sources */, -C3FD8173186DFD9A00FD8ED3 /* Frameworks */, -C3FD8174186DFD9A00FD8ED3 /* Resources */, -); -buildRules = ( -); -dependencies = ( -C3FD817C186DFD9A00FD8ED3 /* PBXTargetDependency */, -); -name = SimpleLineChartTests; -productName = SimpleLineChartTests; -productReference = C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */; -productType = "com.apple.product-type.bundle.unit-test"; -}; + 1E960A801E7DF942000E2BB8 /* TestBed */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1E960A971E7DF942000E2BB8 /* Build configuration list for PBXNativeTarget "TestBed" */; + buildPhases = ( + 1E960A7D1E7DF942000E2BB8 /* Sources */, + 1E960A7E1E7DF942000E2BB8 /* Frameworks */, + 1E960A7F1E7DF942000E2BB8 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TestBed; + productName = TestBed; + productReference = 1E960A811E7DF942000E2BB8 /* TestBed.app */; + productType = "com.apple.product-type.application"; + }; + C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */ = { + isa = PBXNativeTarget; + buildConfigurationList = C3FD8187186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChart" */; + buildPhases = ( + C3FD8151186DFD9A00FD8ED3 /* Sources */, + C3FD8152186DFD9A00FD8ED3 /* Frameworks */, + C3FD8153186DFD9A00FD8ED3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SimpleLineChart; + productName = SimpleLineChart; + productReference = C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */; + productType = "com.apple.product-type.application"; + }; + C3FD8175186DFD9A00FD8ED3 /* SimpleLineChartTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C3FD818A186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChartTests" */; + buildPhases = ( + C3FD8172186DFD9A00FD8ED3 /* Sources */, + C3FD8173186DFD9A00FD8ED3 /* Frameworks */, + C3FD8174186DFD9A00FD8ED3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C3FD817C186DFD9A00FD8ED3 /* PBXTargetDependency */, + ); + name = SimpleLineChartTests; + productName = SimpleLineChartTests; + productReference = C3FD8176186DFD9A00FD8ED3 /* SimpleLineChartTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ -C3FD814D186DFD9A00FD8ED3 /* Project object */ = { -isa = PBXProject; -attributes = { -LastUpgradeCheck = 0800; -ORGANIZATIONNAME = "Boris Emorine"; -TargetAttributes = { -1E960A801E7DF942000E2BB8 = { -CreatedOnToolsVersion = 8.2.1; -DevelopmentTeam = E8XXXD4S77; -ProvisioningStyle = Automatic; -}; -C3FD8154186DFD9A00FD8ED3 = { -DevelopmentTeam = E8XXXD4S77; -}; -C3FD8175186DFD9A00FD8ED3 = { -TestTargetID = C3FD8154186DFD9A00FD8ED3; -}; -}; -}; -buildConfigurationList = C3FD8150186DFD9A00FD8ED3 /* Build configuration list for PBXProject "SimpleLineChart" */; -compatibilityVersion = "Xcode 3.2"; -developmentRegion = English; -hasScannedForEncodings = 0; -knownRegions = ( -en, -Base, -); -mainGroup = C3FD814C186DFD9A00FD8ED3; -productRefGroup = C3FD8156186DFD9A00FD8ED3 /* Products */; -projectDirPath = ""; -projectRoot = ""; -targets = ( -C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */, -C3FD8175186DFD9A00FD8ED3 /* SimpleLineChartTests */, -1E960A801E7DF942000E2BB8 /* TestBed */, -); -}; + C3FD814D186DFD9A00FD8ED3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0800; + ORGANIZATIONNAME = "Boris Emorine"; + TargetAttributes = { + 1E960A801E7DF942000E2BB8 = { + CreatedOnToolsVersion = 8.2.1; + DevelopmentTeam = E8XXXD4S77; + ProvisioningStyle = Automatic; + }; + C3FD8154186DFD9A00FD8ED3 = { + DevelopmentTeam = E8XXXD4S77; + }; + C3FD8175186DFD9A00FD8ED3 = { + TestTargetID = C3FD8154186DFD9A00FD8ED3; + }; + }; + }; + buildConfigurationList = C3FD8150186DFD9A00FD8ED3 /* Build configuration list for PBXProject "SimpleLineChart" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C3FD814C186DFD9A00FD8ED3; + productRefGroup = C3FD8156186DFD9A00FD8ED3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */, + C3FD8175186DFD9A00FD8ED3 /* SimpleLineChartTests */, + 1E960A801E7DF942000E2BB8 /* TestBed */, + ); + }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ -1E960A7F1E7DF942000E2BB8 /* Resources */ = { -isa = PBXResourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -1E960A931E7DF942000E2BB8 /* LaunchScreen.storyboard in Resources */, -1E960A901E7DF942000E2BB8 /* Assets.xcassets in Resources */, -1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */, -1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8153186DFD9A00FD8ED3 /* Resources */ = { -isa = PBXResourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -A64594521BAB257B00D6B8FD /* Launch Screen.storyboard in Resources */, -99B3FA3A1877898B00539A7B /* LICENSE in Resources */, -C3DC80671903845D0080FF06 /* Main.storyboard in Resources */, -C3FD8171186DFD9A00FD8ED3 /* Images.xcassets in Resources */, -99B3FA3B1877898B00539A7B /* README.md in Resources */, -C3FD8163186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8174186DFD9A00FD8ED3 /* Resources */ = { -isa = PBXResourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -C3FD8182186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; + 1E960A7F1E7DF942000E2BB8 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E960A931E7DF942000E2BB8 /* LaunchScreen.storyboard in Resources */, + 1E960A901E7DF942000E2BB8 /* Assets.xcassets in Resources */, + 1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */, + 1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8153186DFD9A00FD8ED3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A64594521BAB257B00D6B8FD /* Launch Screen.storyboard in Resources */, + 99B3FA3A1877898B00539A7B /* LICENSE in Resources */, + C3DC80671903845D0080FF06 /* Main.storyboard in Resources */, + C3FD8171186DFD9A00FD8ED3 /* Images.xcassets in Resources */, + 99B3FA3B1877898B00539A7B /* README.md in Resources */, + C3FD8163186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8174186DFD9A00FD8ED3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C3FD8182186DFD9A00FD8ED3 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ -1E960A7D1E7DF942000E2BB8 /* Sources */ = { -isa = PBXSourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -1E960AA41E7E094D000E2BB8 /* BEMSimpleLineGraphView.m in Sources */, -1E960AA51E7E097C000E2BB8 /* BEMCircle.m in Sources */, -1E960AA61E7E097C000E2BB8 /* BEMLine.m in Sources */, -1E960AA71E7E097C000E2BB8 /* BEMAverageLine.m in Sources */, -1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */, -1E960AA01E7DF9BC000E2BB8 /* ARFontPickerViewController.m in Sources */, -1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */, -1E960AA31E7DF9BC000E2BB8 /* StatsViewController.m in Sources */, -1E960AA21E7DF9BC000E2BB8 /* MasterViewController.m in Sources */, -1E960A851E7DF942000E2BB8 /* main.m in Sources */, -1E960AA11E7DF9BC000E2BB8 /* DetailViewController.m in Sources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8151186DFD9A00FD8ED3 /* Sources */ = { -isa = PBXSourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -C3B90A61187D15F7003E407D /* BEMSimpleLineGraphView.m in Sources */, -C3B90A5F187D15F7003E407D /* BEMCircle.m in Sources */, -C3FD816F186DFD9A00FD8ED3 /* ViewController.m in Sources */, -A6AC895B1C5882DD0052AB1C /* BEMGraphCalculator.m in Sources */, -C3B90A60187D15F7003E407D /* BEMLine.m in Sources */, -C3FD8169186DFD9A00FD8ED3 /* AppDelegate.m in Sources */, -99B15643187B412400B24591 /* StatsViewController.m in Sources */, -A63990B51AD4923900B14D88 /* BEMAverageLine.m in Sources */, -C3FD8165186DFD9A00FD8ED3 /* main.m in Sources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; -C3FD8172186DFD9A00FD8ED3 /* Sources */ = { -isa = PBXSourcesBuildPhase; -buildActionMask = 2147483647; -files = ( -C3FD8184186DFD9A00FD8ED3 /* SimpleLineChartTests.m in Sources */, -C3BCA7E71B8ECCA6007E6090 /* CustomizationTests.m in Sources */, -); -runOnlyForDeploymentPostprocessing = 0; -}; + 1E960A7D1E7DF942000E2BB8 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E960B181E7F9C80000E2BB8 /* MSRGBView.m in Sources */, + 1E960AA41E7E094D000E2BB8 /* BEMSimpleLineGraphView.m in Sources */, + 1E960AA51E7E097C000E2BB8 /* BEMCircle.m in Sources */, + 1E960B1A1E7F9C80000E2BB8 /* MSThumbView.m in Sources */, + 1E960B151E7F9C80000E2BB8 /* MSColorUtils.m in Sources */, + 1E960AA61E7E097C000E2BB8 /* BEMLine.m in Sources */, + 1E960AA71E7E097C000E2BB8 /* BEMAverageLine.m in Sources */, + 1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */, + 1E960AA01E7DF9BC000E2BB8 /* ARFontPickerViewController.m in Sources */, + 1E960B171E7F9C80000E2BB8 /* MSHSBView.m in Sources */, + 1E960B121E7F9C80000E2BB8 /* MSColorComponentView.m in Sources */, + 1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */, + 1E960AA31E7DF9BC000E2BB8 /* StatsViewController.m in Sources */, + 1E960AA21E7DF9BC000E2BB8 /* MasterViewController.m in Sources */, + 1E960B141E7F9C80000E2BB8 /* MSColorSelectionViewController.m in Sources */, + 1E960B161E7F9C80000E2BB8 /* MSColorWheelView.m in Sources */, + 1E960A851E7DF942000E2BB8 /* main.m in Sources */, + 1E960AA11E7DF9BC000E2BB8 /* DetailViewController.m in Sources */, + 1E960B131E7F9C80000E2BB8 /* MSColorSelectionView.m in Sources */, + 1E960B1B1E7F9C80000E2BB8 /* UIControl+HitTestEdgeInsets.m in Sources */, + 1E960B191E7F9C80000E2BB8 /* MSSliderView.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8151186DFD9A00FD8ED3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C3B90A61187D15F7003E407D /* BEMSimpleLineGraphView.m in Sources */, + C3B90A5F187D15F7003E407D /* BEMCircle.m in Sources */, + C3FD816F186DFD9A00FD8ED3 /* ViewController.m in Sources */, + A6AC895B1C5882DD0052AB1C /* BEMGraphCalculator.m in Sources */, + C3B90A60187D15F7003E407D /* BEMLine.m in Sources */, + C3FD8169186DFD9A00FD8ED3 /* AppDelegate.m in Sources */, + 99B15643187B412400B24591 /* StatsViewController.m in Sources */, + A63990B51AD4923900B14D88 /* BEMAverageLine.m in Sources */, + C3FD8165186DFD9A00FD8ED3 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C3FD8172186DFD9A00FD8ED3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C3FD8184186DFD9A00FD8ED3 /* SimpleLineChartTests.m in Sources */, + C3BCA7E71B8ECCA6007E6090 /* CustomizationTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ -C3FD817C186DFD9A00FD8ED3 /* PBXTargetDependency */ = { -isa = PBXTargetDependency; -target = C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */; -targetProxy = C3FD817B186DFD9A00FD8ED3 /* PBXContainerItemProxy */; -}; + C3FD817C186DFD9A00FD8ED3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C3FD8154186DFD9A00FD8ED3 /* SimpleLineChart */; + targetProxy = C3FD817B186DFD9A00FD8ED3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ -1E960A8C1E7DF942000E2BB8 /* Main.storyboard */ = { -isa = PBXVariantGroup; -children = ( -1E960A8D1E7DF942000E2BB8 /* Base */, -); -name = Main.storyboard; -sourceTree = ""; -}; -1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */ = { -isa = PBXVariantGroup; -children = ( -1E960A921E7DF942000E2BB8 /* Base */, -); -name = LaunchScreen.storyboard; -sourceTree = ""; -}; -A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */ = { -isa = PBXVariantGroup; -children = ( -A64594511BAB257B00D6B8FD /* Base */, -); -name = "Launch Screen.storyboard"; -sourceTree = ""; -}; -C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */ = { -isa = PBXVariantGroup; -children = ( -C3FD8162186DFD9A00FD8ED3 /* en */, -); -name = InfoPlist.strings; -sourceTree = ""; -}; -C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */ = { -isa = PBXVariantGroup; -children = ( -C3FD816B186DFD9A00FD8ED3 /* Base */, -); -name = Main.storyboard; -sourceTree = ""; -}; -C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */ = { -isa = PBXVariantGroup; -children = ( -C3FD8181186DFD9A00FD8ED3 /* en */, -); -name = InfoPlist.strings; -sourceTree = ""; -}; + 1E960A8C1E7DF942000E2BB8 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 1E960A8D1E7DF942000E2BB8 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 1E960A921E7DF942000E2BB8 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + A64594501BAB257B00D6B8FD /* Launch Screen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + A64594511BAB257B00D6B8FD /* Base */, + ); + name = "Launch Screen.storyboard"; + sourceTree = ""; + }; + C3FD8161186DFD9A00FD8ED3 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + C3FD8162186DFD9A00FD8ED3 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + C3FD816A186DFD9A00FD8ED3 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + C3FD816B186DFD9A00FD8ED3 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + C3FD8180186DFD9A00FD8ED3 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + C3FD8181186DFD9A00FD8ED3 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ -1E960A951E7DF942000E2BB8 /* Debug */ = { -isa = XCBuildConfiguration; -buildSettings = { -ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -CLANG_ANALYZER_NONNULL = YES; -CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -DEBUG_INFORMATION_FORMAT = dwarf; -DEVELOPMENT_TEAM = E8XXXD4S77; -GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -INFOPLIST_FILE = TestBed/Info.plist; -IPHONEOS_DEPLOYMENT_TARGET = 10.2; -LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; -MTL_ENABLE_DEBUG_INFO = YES; -PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; -PRODUCT_NAME = "$(TARGET_NAME)"; -TARGETED_DEVICE_FAMILY = 2; -}; -name = Debug; -}; -1E960A961E7DF942000E2BB8 /* Release */ = { -isa = XCBuildConfiguration; -buildSettings = { -ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -CLANG_ANALYZER_NONNULL = YES; -CLANG_WARN_DOCUMENTATION_COMMENTS = YES; -COPY_PHASE_STRIP = NO; -DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; -DEVELOPMENT_TEAM = E8XXXD4S77; -GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; -INFOPLIST_FILE = TestBed/Info.plist; -IPHONEOS_DEPLOYMENT_TARGET = 10.2; -LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; -MTL_ENABLE_DEBUG_INFO = NO; -PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; -PRODUCT_NAME = "$(TARGET_NAME)"; -TARGETED_DEVICE_FAMILY = 2; -}; -name = Release; -}; -C3FD8185186DFD9A00FD8ED3 /* Debug */ = { -isa = XCBuildConfiguration; -buildSettings = { -ALWAYS_SEARCH_USER_PATHS = NO; -CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; -CLANG_CXX_LIBRARY = "libc++"; -CLANG_ENABLE_MODULES = YES; -CLANG_ENABLE_OBJC_ARC = YES; -CLANG_WARN_BOOL_CONVERSION = YES; -CLANG_WARN_CONSTANT_CONVERSION = YES; -CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -CLANG_WARN_EMPTY_BODY = YES; -CLANG_WARN_ENUM_CONVERSION = YES; -CLANG_WARN_INFINITE_RECURSION = YES; -CLANG_WARN_INT_CONVERSION = YES; -CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -CLANG_WARN_SUSPICIOUS_MOVE = YES; -CLANG_WARN_UNREACHABLE_CODE = YES; -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; -COPY_PHASE_STRIP = NO; -ENABLE_STRICT_OBJC_MSGSEND = YES; -ENABLE_TESTABILITY = YES; -GCC_C_LANGUAGE_STANDARD = gnu99; -GCC_DYNAMIC_NO_PIC = NO; -GCC_NO_COMMON_BLOCKS = YES; -GCC_OPTIMIZATION_LEVEL = 0; -GCC_PREPROCESSOR_DEFINITIONS = ( -"DEBUG=1", -"$(inherited)", -); -GCC_SYMBOLS_PRIVATE_EXTERN = NO; -GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -GCC_WARN_UNDECLARED_SELECTOR = YES; -GCC_WARN_UNINITIALIZED_AUTOS = YES; -GCC_WARN_UNUSED_FUNCTION = YES; -GCC_WARN_UNUSED_VARIABLE = YES; -IPHONEOS_DEPLOYMENT_TARGET = 8.0; -ONLY_ACTIVE_ARCH = YES; -SDKROOT = iphoneos; -WARNING_CFLAGS = ( -"-Weverything", -"-Wundef", -"-Wextra", -"-Wno-unused-parameter", -"-Wno-objc-missing-property-synthesis", -"-Wpartial-availability", -"-Wno-double-promotion", -"-Wno-direct-ivar-access", -"-Wno-gnu-conditional-omitted-operand", -"-Wno-gnu-statement-expression", -"-Wno-auto-import", -); -}; -name = Debug; -}; -C3FD8186186DFD9A00FD8ED3 /* Release */ = { -isa = XCBuildConfiguration; -buildSettings = { -ALWAYS_SEARCH_USER_PATHS = NO; -CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; -CLANG_CXX_LIBRARY = "libc++"; -CLANG_ENABLE_MODULES = YES; -CLANG_ENABLE_OBJC_ARC = YES; -CLANG_WARN_BOOL_CONVERSION = YES; -CLANG_WARN_CONSTANT_CONVERSION = YES; -CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; -CLANG_WARN_EMPTY_BODY = YES; -CLANG_WARN_ENUM_CONVERSION = YES; -CLANG_WARN_INFINITE_RECURSION = YES; -CLANG_WARN_INT_CONVERSION = YES; -CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; -CLANG_WARN_SUSPICIOUS_MOVE = YES; -CLANG_WARN_UNREACHABLE_CODE = YES; -CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; -"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; -COPY_PHASE_STRIP = YES; -ENABLE_NS_ASSERTIONS = NO; -ENABLE_STRICT_OBJC_MSGSEND = YES; -GCC_C_LANGUAGE_STANDARD = gnu99; -GCC_NO_COMMON_BLOCKS = YES; -GCC_WARN_64_TO_32_BIT_CONVERSION = YES; -GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; -GCC_WARN_UNDECLARED_SELECTOR = YES; -GCC_WARN_UNINITIALIZED_AUTOS = YES; -GCC_WARN_UNUSED_FUNCTION = YES; -GCC_WARN_UNUSED_VARIABLE = YES; -IPHONEOS_DEPLOYMENT_TARGET = 8.0; -SDKROOT = iphoneos; -VALIDATE_PRODUCT = YES; -}; -name = Release; -}; -C3FD8188186DFD9A00FD8ED3 /* Debug */ = { -isa = XCBuildConfiguration; -buildSettings = { -ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; -DEVELOPMENT_TEAM = E8XXXD4S77; -GCC_PRECOMPILE_PREFIX_HEADER = YES; -GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; -INFOPLIST_FILE = "SimpleLineChart/SimpleLineChart-Info.plist"; -IPHONEOS_DEPLOYMENT_TARGET = 9.0; -PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; -PRODUCT_NAME = "$(TARGET_NAME)"; -WARNING_CFLAGS = ( -"$(inherited)", -"-Wno-documentation-unknown-command", -"-Wno-direct-ivar-access", -"-Wno-objc-missing-property-synthesis", -"-Wno-double-promotion", -"-Wno-auto-import", -"-Wno-incomplete-module", -"-Wno-assign-enum", -"-Wno-gnu", -); -WRAPPER_EXTENSION = app; -}; -name = Debug; -}; -C3FD8189186DFD9A00FD8ED3 /* Release */ = { -isa = XCBuildConfiguration; -buildSettings = { -ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; -ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; -DEVELOPMENT_TEAM = E8XXXD4S77; -GCC_PRECOMPILE_PREFIX_HEADER = YES; -GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; -INFOPLIST_FILE = "SimpleLineChart/SimpleLineChart-Info.plist"; -IPHONEOS_DEPLOYMENT_TARGET = 9.0; -PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; -PRODUCT_NAME = "$(TARGET_NAME)"; -WRAPPER_EXTENSION = app; -}; -name = Release; -}; -C3FD818B186DFD9A00FD8ED3 /* Debug */ = { -isa = XCBuildConfiguration; -buildSettings = { -BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleLineChart.app/SimpleLineChart"; -FRAMEWORK_SEARCH_PATHS = ""; -GCC_PRECOMPILE_PREFIX_HEADER = YES; -GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; -GCC_PREPROCESSOR_DEFINITIONS = ( -"DEBUG=1", -"$(inherited)", -); -INFOPLIST_FILE = "SimpleLineChartTests/SimpleLineChartTests-Info.plist"; -PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; -PRODUCT_NAME = "$(TARGET_NAME)"; -TEST_HOST = "$(BUNDLE_LOADER)"; -WARNING_CFLAGS = ( -"$(inherited)", -"-Wno-documentation-unknown-command", -"-Wno-direct-ivar-access", -"-Wno-objc-missing-property-synthesis", -"-Wno-double-promotion", -"-Wno-auto-import", -"-Wno-incomplete-module", -"-Wno-gnu", -"-Wno-assign-enum", -); -WRAPPER_EXTENSION = xctest; -}; -name = Debug; -}; -C3FD818C186DFD9A00FD8ED3 /* Release */ = { -isa = XCBuildConfiguration; -buildSettings = { -BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleLineChart.app/SimpleLineChart"; -FRAMEWORK_SEARCH_PATHS = ""; -GCC_PRECOMPILE_PREFIX_HEADER = YES; -GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; -INFOPLIST_FILE = "SimpleLineChartTests/SimpleLineChartTests-Info.plist"; -PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; -PRODUCT_NAME = "$(TARGET_NAME)"; -TEST_HOST = "$(BUNDLE_LOADER)"; -WARNING_CFLAGS = ( -"$(inherited)", -"-Wno-documentation-unknown-command", -"-Wno-direct-ivar-access", -"-Wno-objc-missing-property-synthesis", -"-Wno-double-promotion", -"-Wno-auto-import", -"-Wno-incomplete-module", -"-Wno-assign-enum", -); -WRAPPER_EXTENSION = xctest; -}; -name = Release; -}; + 1E960A951E7DF942000E2BB8 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = E8XXXD4S77; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = TestBed/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = YES; + PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Debug; + }; + 1E960A961E7DF942000E2BB8 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ANALYZER_NONNULL = YES; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = E8XXXD4S77; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + INFOPLIST_FILE = TestBed/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 10.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = 2; + }; + name = Release; + }; + C3FD8185186DFD9A00FD8ED3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + WARNING_CFLAGS = ( + "-Weverything", + "-Wundef", + "-Wextra", + "-Wno-unused-parameter", + "-Wno-objc-missing-property-synthesis", + "-Wpartial-availability", + "-Wno-double-promotion", + "-Wno-direct-ivar-access", + "-Wno-gnu-conditional-omitted-operand", + "-Wno-gnu-statement-expression", + "-Wno-auto-import", + ); + }; + name = Debug; + }; + C3FD8186186DFD9A00FD8ED3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C3FD8188186DFD9A00FD8ED3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = E8XXXD4S77; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; + INFOPLIST_FILE = "SimpleLineChart/SimpleLineChart-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WARNING_CFLAGS = ( + "$(inherited)", + "-Wno-documentation-unknown-command", + "-Wno-direct-ivar-access", + "-Wno-objc-missing-property-synthesis", + "-Wno-double-promotion", + "-Wno-auto-import", + "-Wno-incomplete-module", + "-Wno-assign-enum", + "-Wno-gnu", + ); + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + C3FD8189186DFD9A00FD8ED3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + DEVELOPMENT_TEAM = E8XXXD4S77; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; + INFOPLIST_FILE = "SimpleLineChart/SimpleLineChart-Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + C3FD818B186DFD9A00FD8ED3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleLineChart.app/SimpleLineChart"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = "SimpleLineChartTests/SimpleLineChartTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WARNING_CFLAGS = ( + "$(inherited)", + "-Wno-documentation-unknown-command", + "-Wno-direct-ivar-access", + "-Wno-objc-missing-property-synthesis", + "-Wno-double-promotion", + "-Wno-auto-import", + "-Wno-incomplete-module", + "-Wno-gnu", + "-Wno-assign-enum", + ); + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + C3FD818C186DFD9A00FD8ED3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/SimpleLineChart.app/SimpleLineChart"; + FRAMEWORK_SEARCH_PATHS = ""; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = "SimpleLineChart/SimpleLineChart-Prefix.pch"; + INFOPLIST_FILE = "SimpleLineChartTests/SimpleLineChartTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "BorisEmorine.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUNDLE_LOADER)"; + WARNING_CFLAGS = ( + "$(inherited)", + "-Wno-documentation-unknown-command", + "-Wno-direct-ivar-access", + "-Wno-objc-missing-property-synthesis", + "-Wno-double-promotion", + "-Wno-auto-import", + "-Wno-incomplete-module", + "-Wno-assign-enum", + ); + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ -1E960A971E7DF942000E2BB8 /* Build configuration list for PBXNativeTarget "TestBed" */ = { -isa = XCConfigurationList; -buildConfigurations = ( -1E960A951E7DF942000E2BB8 /* Debug */, -1E960A961E7DF942000E2BB8 /* Release */, -); -defaultConfigurationIsVisible = 0; -}; -C3FD8150186DFD9A00FD8ED3 /* Build configuration list for PBXProject "SimpleLineChart" */ = { -isa = XCConfigurationList; -buildConfigurations = ( -C3FD8185186DFD9A00FD8ED3 /* Debug */, -C3FD8186186DFD9A00FD8ED3 /* Release */, -); -defaultConfigurationIsVisible = 0; -defaultConfigurationName = Release; -}; -C3FD8187186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChart" */ = { -isa = XCConfigurationList; -buildConfigurations = ( -C3FD8188186DFD9A00FD8ED3 /* Debug */, -C3FD8189186DFD9A00FD8ED3 /* Release */, -); -defaultConfigurationIsVisible = 0; -defaultConfigurationName = Release; -}; -C3FD818A186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChartTests" */ = { -isa = XCConfigurationList; -buildConfigurations = ( -C3FD818B186DFD9A00FD8ED3 /* Debug */, -C3FD818C186DFD9A00FD8ED3 /* Release */, -); -defaultConfigurationIsVisible = 0; -defaultConfigurationName = Release; -}; + 1E960A971E7DF942000E2BB8 /* Build configuration list for PBXNativeTarget "TestBed" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1E960A951E7DF942000E2BB8 /* Debug */, + 1E960A961E7DF942000E2BB8 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C3FD8150186DFD9A00FD8ED3 /* Build configuration list for PBXProject "SimpleLineChart" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C3FD8185186DFD9A00FD8ED3 /* Debug */, + C3FD8186186DFD9A00FD8ED3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C3FD8187186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChart" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C3FD8188186DFD9A00FD8ED3 /* Debug */, + C3FD8189186DFD9A00FD8ED3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C3FD818A186DFD9A00FD8ED3 /* Build configuration list for PBXNativeTarget "SimpleLineChartTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C3FD818B186DFD9A00FD8ED3 /* Debug */, + C3FD818C186DFD9A00FD8ED3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ -}; -rootObject = C3FD814D186DFD9A00FD8ED3 /* Project object */; + }; + rootObject = C3FD814D186DFD9A00FD8ED3 /* Project object */; } diff --git a/Sample Project/TestBed/Base.lproj/Main.storyboard b/Sample Project/TestBed/Base.lproj/Main.storyboard index 42a7487..be6492c 100644 --- a/Sample Project/TestBed/Base.lproj/Main.storyboard +++ b/Sample Project/TestBed/Base.lproj/Main.storyboard @@ -25,7 +25,7 @@ - + @@ -35,11 +35,11 @@ - + - + @@ -81,10 +81,8 @@ - - - - + + @@ -137,16 +135,9 @@ - - - - + @@ -194,7 +185,7 @@ - + @@ -222,7 +213,7 @@ - + @@ -232,13 +223,13 @@ - - + + @@ -259,7 +250,7 @@ - + @@ -268,14 +259,14 @@ - - + @@ -326,7 +317,7 @@ - + @@ -360,7 +351,7 @@ - + @@ -393,7 +384,7 @@ - + @@ -430,7 +421,7 @@ - + @@ -440,13 +431,13 @@ - - + + @@ -467,7 +458,7 @@ - + @@ -504,7 +495,7 @@ - + @@ -541,7 +532,7 @@ - + @@ -574,7 +565,7 @@ - + @@ -607,7 +598,7 @@ - + @@ -644,7 +635,7 @@ - + @@ -681,7 +672,7 @@ - + @@ -690,14 +681,14 @@ - - + @@ -727,14 +718,14 @@ - - + @@ -792,7 +783,7 @@ - + @@ -801,14 +792,14 @@ - - + @@ -870,7 +861,7 @@ - + @@ -900,7 +891,7 @@ - + @@ -937,7 +928,7 @@ - + @@ -978,7 +969,7 @@ - + @@ -987,14 +978,14 @@ - - + @@ -1049,7 +1040,7 @@ - + @@ -1083,7 +1074,7 @@ - + @@ -1113,7 +1104,7 @@ - + @@ -1123,16 +1114,16 @@ - + - + @@ -2298,10 +3025,52 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sample Project/TestBed/DetailViewController.m b/Sample Project/TestBed/DetailViewController.m index 44ecbcf..d358826 100644 --- a/Sample Project/TestBed/DetailViewController.m +++ b/Sample Project/TestBed/DetailViewController.m @@ -46,18 +46,7 @@ - (void)viewDidLoad { [self hydrateDatasets]; - CGFloat sum= 0; - for (NSNumber * number in self.arrayOfValues) { - CGFloat n = number.doubleValue; - - if (n <= BEMNullGraphValue) { - sum += n; - } - } - // The labels to report the values of the graph when the user touches it - self.labelValues.text = [NSString stringWithFormat:@"%i", (int) sum]; - self.labelDates.text = @"between now and later"; - + [self updateLabelsBelowGraph:self.myGraph]; } #pragma mark Data management @@ -82,7 +71,7 @@ - (void)hydrateDatasets { } else { [self.arrayOfDates addObject:[self dateForGraphAfterDate:self.arrayOfDates[i-1]]]; // Dates for the X-Axis of the graph } - if (showNullValue && i == 4) { + if (showNullValue && (i % 4 == 0)) { self.arrayOfValues[i] = @(BEMNullGraphValue); } else { self.totalNumber = self.totalNumber + [[self.arrayOfValues objectAtIndex:i] intValue]; // All of the values added together @@ -129,7 +118,13 @@ - (IBAction)addOrRemovePointFromGraph:(id)sender { - (void) addPointToGraph { // Add point - [self.arrayOfValues addObject:@([self getRandomFloat])]; + NSNumber * newValue ; + if (self.arrayOfValues.count % 4 == 0) { + newValue = @(BEMNullGraphValue); + } else { + newValue = @([self getRandomFloat]); + } + [self.arrayOfValues addObject:newValue]; NSDate *lastDate = self.arrayOfDates.count > 0 ? [self.arrayOfDates lastObject]: [NSDate date]; NSDate *newDate = [self dateForGraphAfterDate:lastDate]; [self.arrayOfDates addObject:newDate]; @@ -144,6 +139,11 @@ - (void) removePointFromGraph { [self.myGraph reloadGraph]; } } +-(NSString *) formatNumber: (NSNumber *) number { + return [NSNumberFormatter localizedStringFromNumber:number + numberStyle:NSNumberFormatterDecimalStyle]; + +} - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { [super prepareForSegue:segue sender:sender]; @@ -151,12 +151,12 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"showStats"]) { BEMGraphCalculator * calc = [BEMGraphCalculator sharedCalculator]; StatsViewController *controller = segue.destinationViewController; - controller.standardDeviation = [NSString stringWithFormat:@"%.2f", [[calc calculateStandardDeviationOnGraph:self.myGraph] doubleValue]]; - controller.average = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueAverageOnGraph:self.myGraph] doubleValue]]; - controller.median = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueMedianOnGraph: self.myGraph] doubleValue]]; - controller.mode = [NSString stringWithFormat:@"%.2f", [[calc calculatePointValueModeOnGraph: self.myGraph] doubleValue]]; - controller.minimum = [NSString stringWithFormat:@"%.2f", [[calc calculateMinimumPointValueOnGraph:self.myGraph] doubleValue]]; - controller.maximum = [NSString stringWithFormat:@"%.2f", [[calc calculateMaximumPointValueOnGraph:self.myGraph] doubleValue]]; + controller.standardDeviation = [self formatNumber:[calc calculateStandardDeviationOnGraph:self.myGraph]]; + controller.average = [self formatNumber:[calc calculatePointValueAverageOnGraph:self.myGraph]]; + controller.median = [self formatNumber:[calc calculatePointValueMedianOnGraph: self.myGraph]]; + controller.mode = [self formatNumber:[calc calculatePointValueModeOnGraph: self.myGraph]]; + controller.minimum = [self formatNumber:[calc calculateMinimumPointValueOnGraph:self.myGraph]]; + controller.maximum = [self formatNumber:[calc calculateMaximumPointValueOnGraph:self.myGraph]]; controller.snapshotImage = [self.myGraph graphSnapshotImage]; } } @@ -165,7 +165,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { #pragma mark - SimpleLineGraph Data Source - (NSUInteger)numberOfPointsInLineGraph:(BEMSimpleLineGraphView *)graph { - return (int)[self.arrayOfValues count]; + return [self.arrayOfValues count]; } - (CGFloat)lineGraph:(BEMSimpleLineGraphView *)graph valueForPointAtIndex:(NSUInteger)index { @@ -233,12 +233,16 @@ - (NSString *)popUpPrefixForlineGraph:(BEMSimpleLineGraphView *)graph { } -(NSString *) popUpTextForlineGraph:(BEMSimpleLineGraphView *)graph atIndex:(NSUInteger)index { - if (!self.popUpText) return @"Invalid format string"; + if (!self.popUpText) return @"Empty format string"; + @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" - return [NSString stringWithFormat: self.popUpText, index]; + return [NSString stringWithFormat: self.popUpText, index]; #pragma clang diagnostic pop - + } @catch (NSException *exception) { + return [NSString stringWithFormat:@"Invalid format string: %@", exception ]; + } + } - (BOOL)lineGraph:(BEMSimpleLineGraphView *)graph alwaysDisplayPopUpAtIndex:(NSUInteger)index { @@ -272,7 +276,7 @@ - (void)lineGraph:(BEMSimpleLineGraphView *)graph modifyPopupView:(UIView *)popu NSAssert (popupView == self.customView, @"View problem"); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" - if (!self.myGraph.formatStringForValues) return; + if (!self.myGraph.formatStringForValues.length) return; self.customViewLabel.text = [NSString stringWithFormat:self.myGraph.formatStringForValues, [self lineGraph:graph valueForPointAtIndex:index] ]; #pragma pop } @@ -326,8 +330,8 @@ - (CGFloat)incrementValueForYAxisOnLineGraph:(BEMSimpleLineGraphView *)graph { #pragma mark Touch handling - (void)lineGraph:(BEMSimpleLineGraphView *)graph didTouchGraphWithClosestIndex:(NSUInteger)index { - self.labelValues.text = [NSString stringWithFormat:@"%@", [self.arrayOfValues objectAtIndex:index]]; - self.labelDates.text = [NSString stringWithFormat:@"in %@", [self labelForDateAtIndex:index]]; + self.labelValues.text = [self formatNumber:[self.arrayOfValues objectAtIndex:index]]; + self.labelDates.text = [NSString stringWithFormat:@"on %@", [self labelForDateAtIndex:index]]; } - (void)lineGraph:(BEMSimpleLineGraphView *)graph didReleaseTouchFromGraphWithClosestIndex:(CGFloat)index { @@ -335,9 +339,7 @@ - (void)lineGraph:(BEMSimpleLineGraphView *)graph didReleaseTouchFromGraphWithCl self.labelValues.alpha = 0.0; self.labelDates.alpha = 0.0; } completion:^(BOOL finished) { - self.labelValues.text = [NSString stringWithFormat:@"%i", [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph].intValue]; - self.labelDates.text = [NSString stringWithFormat:@"between %@ and %@", [self labelForDateAtIndex:0], [self labelForDateAtIndex:self.arrayOfDates.count - 1]]; - + [self updateLabelsBelowGraph:graph]; [UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ self.labelValues.alpha = 1.0; self.labelDates.alpha = 1.0; @@ -345,9 +347,10 @@ - (void)lineGraph:(BEMSimpleLineGraphView *)graph didReleaseTouchFromGraphWithCl }]; } -- (void)lineGraphDidFinishLoading:(BEMSimpleLineGraphView *)graph { +-(void) updateLabelsBelowGraph: (BEMSimpleLineGraphView *)graph { if (self.arrayOfValues.count > 0) { - self.labelValues.text = [NSString stringWithFormat:@"%i", [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph].intValue]; + NSNumber * sum = [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph]; + self.labelValues.text =[self formatNumber:sum]; self.labelDates.text = [NSString stringWithFormat:@"between %@ and %@", [self labelForDateAtIndex:0], [self labelForDateAtIndex:self.arrayOfDates.count - 1]]; } else { self.labelValues.text = @"No data"; @@ -355,4 +358,8 @@ - (void)lineGraphDidFinishLoading:(BEMSimpleLineGraphView *)graph { } } +- (void)lineGraphDidFinishLoading:(BEMSimpleLineGraphView *)graph { + [self updateLabelsBelowGraph:graph]; +} + @end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorComponentView.h b/Sample Project/TestBed/MSColorPicker/MSColorComponentView.h new file mode 100644 index 0000000..0d5008f --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorComponentView.h @@ -0,0 +1,69 @@ +// +// MSColorComponentView.h +// +// Created by Maksym Shcheglov on 2014-02-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@class MSSliderView; + +/** + * The view to edit a color component. + */ +@interface MSColorComponentView : UIControl + +/** + * The title. + */ +@property (nonatomic, copy) NSString *title; + +/** + * The current value. The default value is 0.0. + */ +@property (nonatomic, assign) CGFloat value; + +/** + * The minimum value. The default value is 0.0. + */ +@property (nonatomic, assign) CGFloat minimumValue; + +/** + * The maximum value. The default value is 255.0. + */ +@property (nonatomic, assign) CGFloat maximumValue; + +/** + * The format string to use apply for textfield value. \c %.f by default. + */ +@property (nonatomic, copy) NSString *format; + +/** + * Sets the array of CGColorRef objects defining the color of each gradient stop on a slider's track. + * The location of each gradient stop is evaluated with formula: i * width_of_the_track / number_of_colors. + * + * @param colors An array of CGColorRef objects. + */ +- (void)setColors:(NSArray *)colors __attribute__((nonnull(1))); + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorComponentView.m b/Sample Project/TestBed/MSColorPicker/MSColorComponentView.m new file mode 100644 index 0000000..4ef9122 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorComponentView.m @@ -0,0 +1,222 @@ +// +// MSColorComponentView.m +// +// Created by Maksym Shcheglov on 2014-02-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSColorComponentView.h" +#import "MSSliderView.h" + +// Temporary disabled the color component editing via text field +#define COLOR_TEXT_FIELD_ENABLED + +extern CGFloat const MSRGBColorComponentMaxValue; +static CGFloat const MSColorComponentViewSpacing = 5.0f; +static CGFloat const MSColorComponentLabelWidth = 60.0f; +static CGFloat const MSColorComponentTextFieldWidth = 50.0f; + +@interface MSColorComponentView () +{ + @private + + UILabel *_label; + MSSliderView *_slider; // The color slider to edit color component. + UITextField *_textField; +} + +@end + +@implementation MSColorComponentView + ++ (BOOL)requiresConstraintBasedLayout +{ + return YES; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (void)setTitle:(NSString *)title +{ + _label.text = title; +} + +- (void)setMinimumValue:(CGFloat)minimumValue +{ + _slider.minimumValue = minimumValue; +} + +- (void)setMaximumValue:(CGFloat)maximumValue +{ + _slider.maximumValue = maximumValue; +} + +- (void)setValue:(CGFloat)value +{ + _slider.value = value; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-nonliteral" + + _textField.text = [NSString stringWithFormat:_format, value]; +#pragma clang diagnostic pop +} + +- (NSString *)title +{ + return _label.text; +} + +- (CGFloat)minimumValue +{ + return _slider.minimumValue; +} + +- (CGFloat)maximumValue +{ + return _slider.maximumValue; +} + +- (CGFloat)value +{ + return _slider.value; +} + +#pragma mark - UITextFieldDelegate methods + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + [self setValue:[textField.text floatValue]]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; +} + +- (BOOL)textFieldShouldReturn:(UITextField *)textField +{ + [textField resignFirstResponder]; + return YES; +} + +- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string +{ + NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string]; + + //first, check if the new string is numeric only. If not, return NO; + NSCharacterSet *characterSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789,."] invertedSet]; + + if ([newString rangeOfCharacterFromSet:characterSet].location != NSNotFound) { + return NO; + } + + return [newString floatValue] <= _slider.maximumValue; +} + +- (void)setColors:(NSArray *)colors +{ + NSParameterAssert(colors); + [_slider setColors:colors]; +} + +#pragma mark - Private methods + +- (void)ms_baseInit +{ + self.accessibilityLabel = @"color_component_view"; + + _format = @"%.f"; + + _label = [[UILabel alloc] init]; + _label.translatesAutoresizingMaskIntoConstraints = NO; + _label.adjustsFontSizeToFitWidth = YES; + [self addSubview:_label]; + + _slider = [[MSSliderView alloc] init]; + _slider.maximumValue = MSRGBColorComponentMaxValue; + _slider.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_slider]; + +#ifdef COLOR_TEXT_FIELD_ENABLED + _textField = [[UITextField alloc] init]; + _textField.borderStyle = UITextBorderStyleRoundedRect; + _textField.translatesAutoresizingMaskIntoConstraints = NO; + [_textField setKeyboardType:UIKeyboardTypeNumbersAndPunctuation]; + [self addSubview:_textField]; +#endif + + [self setValue:0.0f]; + [_slider addTarget:self action:@selector(ms_didChangeSliderValue:) forControlEvents:UIControlEventValueChanged]; + [_textField setDelegate:self]; + + [self ms_installConstraints]; +} + +- (void)ms_didChangeSliderValue:(MSSliderView *)sender +{ + [self setValue:sender.value]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; +} + +- (void)ms_installConstraints +{ +#ifdef COLOR_TEXT_FIELD_ENABLED + NSDictionary *views = @{ @"label": _label, @"slider": _slider, @"textField": _textField }; + NSDictionary *metrics = @{ @"spacing": @(MSColorComponentViewSpacing), + @"label_width": @(MSColorComponentLabelWidth), + @"textfield_width": @(MSColorComponentTextFieldWidth) }; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label(label_width)]-spacing-[slider]-spacing-[textField(textfield_width)]|" + options:NSLayoutFormatAlignAllCenterY + metrics:metrics + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]|" options:0 metrics:nil views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[textField]|" options:0 metrics:nil views:views]]; +#else + NSDictionary *views = @{ @"label": _label, @"slider": _slider }; + NSDictionary *metrics = @{ @"spacing": @(MSColorComponentViewSpacing), + @"label_width": @(MSColorComponentLabelWidth) }; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[label(label_width)]-spacing-[slider]-spacing-|" + options:NSLayoutFormatAlignAllCenterY + metrics:metrics + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[label]|" options:0 metrics:nil views:views]]; + +#endif +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorPicker.h b/Sample Project/TestBed/MSColorPicker/MSColorPicker.h new file mode 100644 index 0000000..5cc8e02 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorPicker.h @@ -0,0 +1,29 @@ +// +// MSColorPicker.h +// +// Created by Maksym Shcheglov on 2015-03-06. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +#import + +#import "MSColorSelectionViewController.h" +#import "MSColorUtils.h" diff --git a/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.h b/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.h new file mode 100644 index 0000000..e1b3830 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.h @@ -0,0 +1,63 @@ +// +// MSColorSelectionView.h +// +// Created by Maksym Shcheglov on 2015-04-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "MSColorView.h" + +/** + * The enum to define the MSColorView's types. + */ +typedef NS_ENUM(NSUInteger, MSSelectedColorView) { + /** + * The RGB color view type. + */ + MSSelectedColorViewRGB, + /** + * The HSB color view type. + */ + MSSelectedColorViewHSB +}; + +/** + * The MSColorSelectionView aggregates views that should be used to edit color components. + */ +@interface MSColorSelectionView : UIView + +/** + * The selected color view + */ +@property (nonatomic, assign, readonly) MSSelectedColorView selectedIndex; + +/** + * Makes a color component view (rgb or hsb) visible according to the index. + * + * @param index This index define a view to show. + * @param animated If YES, the view is being appeared using an animation. + */ +- (void)setSelectedIndex:(MSSelectedColorView)index animated:(BOOL)animated; + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.m b/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.m new file mode 100644 index 0000000..39f7b72 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorSelectionView.m @@ -0,0 +1,126 @@ +// +// MSColorSelectionView.m +// +// Created by Maksym Shcheglov on 2015-04-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSColorSelectionView.h" +#import "MSRGBView.h" +#import "MSHSBView.h" + +@interface MSColorSelectionView () + +@property (nonatomic, strong) UIView *rgbColorView; +@property (nonatomic, strong) UIView *hsbColorView; +@property (nonatomic, assign) MSSelectedColorView selectedIndex; + +@end + +@implementation MSColorSelectionView + +@synthesize color = _color; +@synthesize delegate; + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self != nil) { + [self ms_init]; + } + + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self != nil) { + [self ms_init]; + } + + return self; +} + +- (void)setColor:(UIColor *)color +{ + _color = color; + [[self selectedView] setColor:color]; +} + +- (void)setSelectedIndex:(MSSelectedColorView)index animated:(BOOL)animated +{ + self.selectedIndex = index; + if (self.color) self.selectedView.color = self.color; + [UIView animateWithDuration:animated ? .5 : 0.0 animations:^{ + self.rgbColorView.alpha = index == 0 ? 1.0 : 0.0; + self.hsbColorView.alpha = index == 1 ? 1.0 : 0.0; + } completion:nil]; +} + +- (UIView *)selectedView +{ + return self.selectedIndex == 0 ? self.rgbColorView : self.hsbColorView; +} + +- (void)addColorView:(UIView *)view +{ + view.delegate = self; + [self addSubview:view]; + view.translatesAutoresizingMaskIntoConstraints = NO; + NSDictionary *views = NSDictionaryOfVariableBindings(view); + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:views]]; +} + +- (void)updateConstraints +{ + [self.rgbColorView setNeedsUpdateConstraints]; + [self.hsbColorView setNeedsUpdateConstraints]; + [super updateConstraints]; +} + +#pragma mark - FBColorViewDelegate methods + +- (void)colorView:(id)colorView didChangeColor:(UIColor *)color +{ + self.color = color; + [self.delegate colorView:self didChangeColor:self.color]; +} + +#pragma mark - Private + +- (void)ms_init +{ + self.accessibilityLabel = @"color_selection_view"; + + self.backgroundColor = [UIColor whiteColor]; + self.rgbColorView = [[MSRGBView alloc] init]; + self.hsbColorView = [[MSHSBView alloc] init]; + [self addColorView:self.rgbColorView]; + [self addColorView:self.hsbColorView]; + [self setSelectedIndex:0 animated:NO]; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.h b/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.h new file mode 100644 index 0000000..0f4fe7f --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.h @@ -0,0 +1,64 @@ +// +// MSColorSelectionViewController.h +// +// Created by Maksym Shcheglov on 2015-04-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class MSColorSelectionViewController; + +/** + * The delegate of a MSColorSelectionViewController object must adopt the MSColorSelectionViewController protocol. + * Methods of the protocol allow the delegate to handle color value changes. + */ +@protocol MSColorSelectionViewControllerDelegate + +@required + +/** + * Tells the data source to return the color components. + * + * @param colorViewCntroller The color view. + * @param color The new color value. + */ +- (void)colorViewController:(MSColorSelectionViewController *)colorViewCntroller didChangeColor:(UIColor *)color; + +@end + +@interface MSColorSelectionViewController : UIViewController + +/** + * The controller's delegate. Controller notifies a delegate on color change. + */ +@property (nonatomic, weak, nullable) id delegate; +/** + * The current color value. + */ +@property (nonatomic, strong) UIColor *color; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.m b/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.m new file mode 100644 index 0000000..0528776 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorSelectionViewController.m @@ -0,0 +1,92 @@ +// +// MSColorSelectionViewController.m +// +// Created by Maksym Shcheglov on 2015-04-12. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSColorSelectionViewController.h" +#import "MSColorSelectionView.h" + +#import "MSColorPicker.h" + +@interface MSColorSelectionViewController () + +@end + +@implementation MSColorSelectionViewController + +- (void)loadView +{ + MSColorSelectionView *colorSelectionView = [[MSColorSelectionView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + + self.view = colorSelectionView; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + UISegmentedControl *segmentControl = [[UISegmentedControl alloc] initWithItems:@[NSLocalizedString(@"RGB", ), NSLocalizedString(@"HSB", )]]; + [segmentControl addTarget:self action:@selector(segmentControlDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + segmentControl.selectedSegmentIndex = 0; + self.navigationItem.titleView = segmentControl; + + [self.colorSelectionView setSelectedIndex:0 animated:NO]; + self.colorSelectionView.delegate = self; + self.edgesForExtendedLayout = UIRectEdgeNone; +} + +- (IBAction)segmentControlDidChangeValue:(UISegmentedControl *)segmentedControl +{ + [self.colorSelectionView setSelectedIndex:segmentedControl.selectedSegmentIndex animated:YES]; +} + +- (void)setColor:(UIColor *)color +{ + self.colorSelectionView.color = color; +} + +- (UIColor *)color +{ + return self.colorSelectionView.color; +} + +- (void)viewWillLayoutSubviews +{ + [self.colorSelectionView setNeedsUpdateConstraints]; + [self.colorSelectionView updateConstraintsIfNeeded]; +} + +- (MSColorSelectionView *)colorSelectionView +{ + return (MSColorSelectionView *)self.view; +} + +#pragma mark - MSColorViewDelegate + +- (void)colorView:(id)colorView didChangeColor:(UIColor *)color +{ + [self.delegate colorViewController:self didChangeColor:color]; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorUtils.h b/Sample Project/TestBed/MSColorPicker/MSColorUtils.h new file mode 100644 index 0000000..3a6df99 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorUtils.h @@ -0,0 +1,96 @@ +// +// MSColorUtils.h +// +// Created by Maksym Shcheglov on 2014-02-13. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +/** + * The structure to represent a color in the Red-Green-Blue-Alpha color space. + */ +typedef struct { CGFloat red, green, blue, alpha; } RGB; +/** + * The structure to represent a color in the hue-saturation-brightness color space. + */ +typedef struct { CGFloat hue, saturation, brightness, alpha; } HSB; + +/** + * The maximum value of the RGB color components. + */ +extern CGFloat const MSRGBColorComponentMaxValue; +/** + * The maximum value of the alpha component. + */ +extern CGFloat const MSAlphaComponentMaxValue; +/** + * The maximum value of the HSB color components. + */ +extern CGFloat const MSHSBColorComponentMaxValue; + +/** + * Converts an RGB color value to HSV. + * Assumes r, g, and b are contained in the set [0, 1] and + * returns h, s, and b in the set [0, 1]. + * + * @param rgb The rgb color values + * @return The hsb color values + */ +extern HSB MSRGB2HSB(RGB rgb); + +/** + * Converts an HSB color value to RGB. + * Assumes h, s, and b are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param hsb The hsb color values + * @return The rgb color values + */ +extern RGB MSHSB2RGB(HSB hsb); + +/** + * Returns the rgb values of the color components. + * + * @param color The color value. + * + * @return The values of the color components (including alpha). + */ +extern RGB MSRGBColorComponents(UIColor *color); + +/** + * Converts hex string to the UIColor representation. + * + * @param color The color value. + * + * @return The hex string color value. + */ +extern NSString * MSHexStringFromColor(UIColor *color); + +/** + * Converts UIColor value to the hex string. + * + * @param hexString The hex string color value. + * + * @return The color value. + */ +extern UIColor * MSColorFromHexString(NSString *hexString); diff --git a/Sample Project/TestBed/MSColorPicker/MSColorUtils.m b/Sample Project/TestBed/MSColorPicker/MSColorUtils.m new file mode 100644 index 0000000..01ae580 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorUtils.m @@ -0,0 +1,177 @@ +// +// MSColorUtils.m +// +// Created by Maksym Shcheglov on 2014-02-13. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSColorUtils.h" + +CGFloat const MSRGBColorComponentMaxValue = 255.0f; +CGFloat const MSAlphaComponentMaxValue = 100.0f; +CGFloat const MSHSBColorComponentMaxValue = 1.0f; + +extern HSB MSRGB2HSB(RGB rgb) +{ + HSB hsb = { 0.0f, 0.0f, 0.0f, 0.0f }; + double rd = (double)rgb.red; + double gd = (double)rgb.green; + double bd = (double)rgb.blue; + double max = fmax(rd, fmax(gd, bd)); + double min = fmin(rd, fmin(gd, bd)); + double h = 0, s, b = max; + + double d = max - min; + + s = max <= 0 ? 0 : d / max; + + if (max <= min) { + h = 0; // achromatic + } else { + if (max <= rd) { + h = (gd - bd) / d + (gd < bd ? 6 : 0); + } else if (max <= gd) { + h = (bd - rd) / d + 2; + } else if (max <= bd) { + h = (rd - gd) / d + 4; + } + + h /= 6; + } + + hsb.hue = h; + hsb.saturation = s; + hsb.brightness = b; + hsb.alpha = rgb.alpha; + return hsb; +} + +extern RGB MSHSB2RGB(HSB hsb) +{ + RGB rgb = { 0.0f, 0.0f, 0.0f, 0.0f }; + double r, g, b; + + int i = hsb.hue * 6; + double f = hsb.hue * 6 - i; + double p = hsb.brightness * (1 - hsb.saturation); + double q = hsb.brightness * (1 - f * hsb.saturation); + double t = hsb.brightness * (1 - (1 - f) * hsb.saturation); + + switch (i % 6) { + case 0: r = hsb.brightness, g = t, b = p; break; + + case 1: r = q, g = hsb.brightness, b = p; break; + + case 2: r = p, g = hsb.brightness, b = t; break; + + case 3: r = p, g = q, b = hsb.brightness; break; + + case 4: r = t, g = p, b = hsb.brightness; break; + + case 5: + default: + r = hsb.brightness, g = p, b = q; break; + } + + rgb.red = r; + rgb.green = g; + rgb.blue = b; + rgb.alpha = hsb.alpha; + return rgb; +} + +extern RGB MSRGBColorComponents(UIColor *color) +{ + RGB result = {0.0, 0.0, 0.0, 0.0}; + CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)); + + if (colorSpaceModel != kCGColorSpaceModelRGB && colorSpaceModel != kCGColorSpaceModelMonochrome) { + return result; + } + + const CGFloat *components = CGColorGetComponents(color.CGColor); + + if (colorSpaceModel == kCGColorSpaceModelMonochrome) { + result.red = result.green = result.blue = components[0]; + result.alpha = components[1]; + } else { + result.red = components[0]; + result.green = components[1]; + result.blue = components[2]; + result.alpha = components[3]; + } + + return result; +} + +extern NSString * MSHexStringFromColor(UIColor *color) +{ + CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(CGColorGetColorSpace(color.CGColor)); + + if (colorSpaceModel != kCGColorSpaceModelRGB && colorSpaceModel != kCGColorSpaceModelMonochrome) { + return nil; + } + + const CGFloat *components = CGColorGetComponents(color.CGColor); + CGFloat red, green, blue, alpha; + + if (colorSpaceModel == kCGColorSpaceModelMonochrome) { + red = green = blue = components[0]; + alpha = components[1]; + } else { + red = components[0]; + green = components[1]; + blue = components[2]; + alpha = components[3]; + } + + NSString *hexColorString = [NSString stringWithFormat:@"#%02lX%02lX%02lX%02lX", + (unsigned long)(red * MSRGBColorComponentMaxValue), + (unsigned long)(green * MSRGBColorComponentMaxValue), + (unsigned long)(blue * MSRGBColorComponentMaxValue), + (unsigned long)(alpha * MSRGBColorComponentMaxValue)]; + return hexColorString; +} + +extern UIColor * MSColorFromHexString(NSString *hexColor) +{ + if (![hexColor hasPrefix:@"#"]) { + return nil; + } + + NSScanner *scanner = [NSScanner scannerWithString:hexColor]; + [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"#"]]; + + unsigned hexNum; + + if (![scanner scanHexInt:&hexNum]) return nil; + + int r = (hexNum >> 24) & 0xFF; + int g = (hexNum >> 16) & 0xFF; + int b = (hexNum >> 8) & 0xFF; + int a = (hexNum) & 0xFF; + + return [UIColor colorWithRed:r / MSRGBColorComponentMaxValue + green:g / MSRGBColorComponentMaxValue + blue:b / MSRGBColorComponentMaxValue + alpha:a / MSRGBColorComponentMaxValue]; +} diff --git a/Sample Project/TestBed/MSColorPicker/MSColorView.h b/Sample Project/TestBed/MSColorPicker/MSColorView.h new file mode 100644 index 0000000..14e8692 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorView.h @@ -0,0 +1,65 @@ +// +// MSColorView.h +// +// Created by Maksym Shcheglov on 2014-02-15. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +@protocol MSColorViewDelegate; + +/** + * The \c MSColorView protocol declares a view's interface for displaying and editing color value. + */ +@protocol MSColorView + +@required + +/** + * The object that acts as the delegate of the receiving color selection view. + */ +@property (nonatomic, weak) id delegate; +/** + * The current color. + */ +@property (nonatomic, strong) UIColor* color; + +@end + +/** + * The delegate of a MSColorView object must adopt the MSColorViewDelegate protocol. + * Methods of the protocol allow the delegate to handle color value changes. + */ +@protocol MSColorViewDelegate + +@required + +/** + * Tells the data source to return the color components. + * + * @param colorView The color view. + * @param color The new color value. + */ +- (void)colorView:(id)colorView didChangeColor:(UIColor *)color; + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorWheelView.h b/Sample Project/TestBed/MSColorPicker/MSColorWheelView.h new file mode 100644 index 0000000..2e61117 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorWheelView.h @@ -0,0 +1,45 @@ +// +// MSColorWheelView.h +// +// Created by Maksym Shcheglov on 2014-02-04. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import +#import + +/** + * The color wheel view. + */ +@interface MSColorWheelView : UIControl + +/** + * The hue value. + */ +@property (nonatomic, assign) CGFloat hue; + +/** + * The saturation value. + */ +@property (nonatomic, assign) CGFloat saturation; + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSColorWheelView.m b/Sample Project/TestBed/MSColorPicker/MSColorWheelView.m new file mode 100644 index 0000000..803be2d --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSColorWheelView.m @@ -0,0 +1,240 @@ +// +// MSColorWheelView.m +// +// Created by Maksym Shcheglov on 2014-02-04. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSColorWheelView.h" +#import "MSColorUtils.h" + +@interface MSColorWheelView () +{ + @private + + CALayer *_indicatorLayer; + CGFloat _hue; + CGFloat _saturation; +} + +@end + +@implementation MSColorWheelView + ++ (BOOL)requiresConstraintBasedLayout +{ + return YES; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + _hue = 0.0f; + _saturation = 0.0f; + + self.accessibilityLabel = @"color_wheel_view"; + + self.layer.delegate = self; + [self.layer addSublayer:[self indicatorLayer]]; + + // [self setSelectedPoint:CGPointMake(dimension / 2, dimension / 2)]; + } + + return self; +} + +- (CALayer *)indicatorLayer +{ + if (!_indicatorLayer) { + CGFloat dimension = 33; + UIColor *edgeColor = [UIColor colorWithWhite:0.9 alpha:0.8]; + _indicatorLayer = [CALayer layer]; + _indicatorLayer.cornerRadius = dimension / 2; + _indicatorLayer.borderColor = edgeColor.CGColor; + _indicatorLayer.borderWidth = 2; + _indicatorLayer.backgroundColor = [UIColor whiteColor].CGColor; + _indicatorLayer.bounds = CGRectMake(0, 0, dimension, dimension); + _indicatorLayer.position = CGPointMake(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2); + _indicatorLayer.shadowColor = [UIColor blackColor].CGColor; + _indicatorLayer.shadowOffset = CGSizeZero; + _indicatorLayer.shadowRadius = 1; + _indicatorLayer.shadowOpacity = 0.5f; + } + + return _indicatorLayer; +} + +- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGPoint position = [[touches anyObject] locationInView:self]; + + [self onTouchEventWithPosition:position]; +} + +- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGPoint position = [[touches anyObject] locationInView:self]; + + [self onTouchEventWithPosition:position]; +} + +- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event +{ + CGPoint position = [[touches anyObject] locationInView:self]; + + [self onTouchEventWithPosition:position]; +} + +- (void)onTouchEventWithPosition:(CGPoint)point +{ + CGFloat radius = CGRectGetWidth(self.bounds) / 2; + CGFloat dist = sqrtf((radius - point.x) * (radius - point.x) + (radius - point.y) * (radius - point.y)); + + if (dist <= radius) { + [self ms_colorWheelValueWithPosition:point hue:&_hue saturation:&_saturation]; + [self setSelectedPoint:point]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; + } +} + +- (void)setSelectedPoint:(CGPoint)point +{ + UIColor *selectedColor = [UIColor colorWithHue:_hue saturation:_saturation brightness:1.0f alpha:1.0f]; + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + self.indicatorLayer.position = point; + self.indicatorLayer.backgroundColor = selectedColor.CGColor; + [CATransaction commit]; +} + +- (void)setHue:(CGFloat)hue +{ + _hue = hue; + [self setSelectedPoint:[self ms_selectedPoint]]; + [self setNeedsDisplay]; +} + +- (void)setSaturation:(CGFloat)saturation +{ + _saturation = saturation; + [self setSelectedPoint:[self ms_selectedPoint]]; + [self setNeedsDisplay]; +} + +#pragma mark - CALayerDelegate methods + +- (void)displayLayer:(CALayer *)layer +{ + CGFloat dimension = MIN(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)); + CFMutableDataRef bitmapData = CFDataCreateMutable(NULL, 0); + + CFDataSetLength(bitmapData, dimension * dimension * 4); + [self ms_colorWheelBitmap:CFDataGetMutableBytePtr(bitmapData) withSize:CGSizeMake(dimension, dimension)]; + id image = [self ms_imageWithRGBAData:bitmapData width:dimension height:dimension]; + CFRelease(bitmapData); + self.layer.contents = image; +} + +- (void)layoutSublayersOfLayer:(CALayer *)layer +{ + if (layer == self.layer) { + [self setSelectedPoint:[self ms_selectedPoint]]; + [self.layer setNeedsDisplay]; + } +} + +#pragma mark - Private methods + +- (CGPoint)ms_selectedPoint +{ + CGFloat dimension = MIN(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame)); + CGFloat radius = _saturation * dimension / 2; + CGFloat x = dimension / 2 + radius * cosf(_hue * M_PI * 2.0f); + CGFloat y = dimension / 2 + radius * sinf(_hue * M_PI * 2.0f); + + return CGPointMake(x, y); +} + +- (void)ms_colorWheelBitmap:(out UInt8 *)bitmap withSize:(CGSize)size +{ + for (NSUInteger y = 0; y < size.width; y++) { + for (NSUInteger x = 0; x < size.height; x++) { + CGFloat hue, saturation, a = 0.0f; + [self ms_colorWheelValueWithPosition:CGPointMake(x, y) hue:&hue saturation:&saturation]; + RGB rgb = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (saturation < 1.0) { + // Antialias the edge of the circle. + if (saturation > 0.99) { + a = (1.0 - saturation) * 100; + } else { + a = 1.0; + } + + HSB hsb = { hue, saturation, 1.0f, a }; + rgb = MSHSB2RGB(hsb); + } + + NSInteger i = 4 * (x + y * size.width); + bitmap[i] = rgb.red * 0xff; + bitmap[i + 1] = rgb.green * 0xff; + bitmap[i + 2] = rgb.blue * 0xff; + bitmap[i + 3] = rgb.alpha * 0xff; + } + } +} + +- (void)ms_colorWheelValueWithPosition:(CGPoint)position hue:(out CGFloat *)hue saturation:(out CGFloat *)saturation +{ + NSInteger c = CGRectGetWidth(self.bounds) / 2; + CGFloat dx = (float)(position.x - c) / c; + CGFloat dy = (float)(position.y - c) / c; + CGFloat d = sqrtf((float)(dx * dx + dy * dy)); + + *saturation = d; + + if (d <= 0) { + *hue = 0; + } else { + *hue = acosf((float)dx / d) / M_PI / 2.0f; + + if (dy < 0) { + *hue = 1.0 - *hue; + } + } +} + +- (id)ms_imageWithRGBAData:(CFDataRef)data width:(NSUInteger)width height:(NSUInteger)height +{ + CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData(data); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGImageRef imageRef = CGImageCreate(width, height, 8, 32, width * 4, colorSpace, (CGBitmapInfo) (kCGBitmapByteOrderDefault | kCGImageAlphaLast), dataProvider, NULL, 0, kCGRenderingIntentDefault); + + CGDataProviderRelease(dataProvider); + CGColorSpaceRelease(colorSpace); + return (__bridge_transfer id)imageRef; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSHSBView.h b/Sample Project/TestBed/MSColorPicker/MSHSBView.h new file mode 100644 index 0000000..ca996db --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSHSBView.h @@ -0,0 +1,36 @@ +// +// MSHSBView.h +// +// Created by Maksym Shcheglov on 2014-02-17. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "MSColorView.h" + +/** + * The view to edit HSB color components. + */ +@interface MSHSBView : UIView + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSHSBView.m b/Sample Project/TestBed/MSColorPicker/MSHSBView.m new file mode 100644 index 0000000..3578779 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSHSBView.m @@ -0,0 +1,229 @@ +// +// MSHSBView.m +// +// Created by Maksym Shcheglov on 2014-02-17. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSHSBView.h" +#import "MSColorWheelView.h" +#import "MSColorComponentView.h" +#import "MSSliderView.h" +#import "MSColorUtils.h" + +extern CGFloat const MSAlphaComponentMaxValue; +extern CGFloat const MSHSBColorComponentMaxValue; + +static CGFloat const MSColorSampleViewHeight = 30.0f; +static CGFloat const MSViewMargin = 20.0f; +static CGFloat const MSColorWheelDimension = 200.0f; + +@interface MSHSBView () +{ + @private + + MSColorWheelView *_colorWheel; + MSColorComponentView *_brightnessView; + UIView *_colorSample; + + HSB _colorComponents; + NSArray *_layoutConstraints; +} + +@end + +@implementation MSHSBView + +@synthesize delegate = _delegate; + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (void)reloadData +{ + [_colorSample setBackgroundColor:self.color]; + [_colorSample setAccessibilityValue:MSHexStringFromColor(self.color)]; + [self ms_reloadViewsWithColorComponents:_colorComponents]; +} + +- (void)setColor:(UIColor *)color +{ + _colorComponents = MSRGB2HSB(MSRGBColorComponents(color)); + [self reloadData]; +} + +- (UIColor *)color +{ + return [UIColor colorWithHue:_colorComponents.hue saturation:_colorComponents.saturation brightness:_colorComponents.brightness alpha:_colorComponents.alpha]; +} + +- (void)updateConstraints +{ + [self ms_updateConstraints]; + [super updateConstraints]; +} + +#pragma mark - Private methods + +- (void)ms_baseInit +{ + self.accessibilityLabel = @"hsb_view"; + + _colorSample = [[UIView alloc] init]; + _colorSample.accessibilityLabel = @"color_sample"; + _colorSample.layer.borderColor = [UIColor blackColor].CGColor; + _colorSample.layer.borderWidth = .5f; + _colorSample.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_colorSample]; + + _colorWheel = [[MSColorWheelView alloc] init]; + _colorWheel.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_colorWheel]; + + _brightnessView = [[MSColorComponentView alloc] init]; + _brightnessView.title = NSLocalizedString(@"Brightness", ); + _brightnessView.maximumValue = MSHSBColorComponentMaxValue; + _brightnessView.format = @"%.2f"; + _brightnessView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_brightnessView]; + + [_colorWheel addTarget:self action:@selector(ms_colorDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + [_brightnessView addTarget:self action:@selector(ms_brightnessDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + + [self setNeedsUpdateConstraints]; +} + +- (void)ms_updateConstraints +{ + // remove all constraints first + if (_layoutConstraints != nil) { + [self removeConstraints:_layoutConstraints]; + } + + _layoutConstraints = self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact ? [self ms_constraintsForCompactVerticalSizeClass] : [self ms_constraintsForRegularVerticalSizeClass]; + [self addConstraints:_layoutConstraints]; +} + +- (NSArray *)ms_constraintsForRegularVerticalSizeClass +{ + NSDictionary *metrics = @{ @"margin": @(MSViewMargin), + @"height": @(MSColorSampleViewHeight), + @"color_wheel_dimension": @(MSColorWheelDimension) }; + + NSDictionary *views = NSDictionaryOfVariableBindings(_colorSample, _colorWheel, _brightnessView); + NSMutableArray *layoutConstraints = [NSMutableArray array]; + + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_colorSample]-margin-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_colorWheel(>=color_wheel_dimension)]-margin-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_brightnessView]-margin-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-margin-[_colorSample(height)]-margin-[_colorWheel]-margin-[_brightnessView]-(>=margin@250)-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObject:[NSLayoutConstraint + constraintWithItem:_colorWheel + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:_colorWheel + attribute:NSLayoutAttributeHeight + multiplier:1.0f + constant:0]]; + return layoutConstraints; +} + +- (NSArray *)ms_constraintsForCompactVerticalSizeClass +{ + NSDictionary *metrics = @{ @"margin": @(MSViewMargin), + @"height": @(MSColorSampleViewHeight), + @"color_wheel_dimension": @(MSColorWheelDimension) }; + + NSDictionary *views = NSDictionaryOfVariableBindings(_colorSample, _colorWheel, _brightnessView); + NSMutableArray *layoutConstraints = [NSMutableArray array]; + + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_colorSample]-margin-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_colorWheel(>=color_wheel_dimension)]-margin-[_brightnessView]-(margin@500)-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-margin-[_colorSample(height)]-margin-[_colorWheel]-(margin@500)-|" options:0 metrics:metrics views:views]]; + [layoutConstraints addObject:[NSLayoutConstraint + constraintWithItem:_colorWheel + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:_colorWheel + attribute:NSLayoutAttributeHeight + multiplier:1.0f + constant:0]]; + [layoutConstraints addObject:[NSLayoutConstraint + constraintWithItem:_brightnessView + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:self + attribute:NSLayoutAttributeCenterY + multiplier:1.0f + constant:0]]; + return layoutConstraints; +} + +- (void)ms_reloadViewsWithColorComponents:(HSB)colorComponents +{ + _colorWheel.hue = colorComponents.hue; + _colorWheel.saturation = colorComponents.saturation; + [self ms_updateSlidersWithColorComponents:colorComponents]; +} + +- (void)ms_updateSlidersWithColorComponents:(HSB)colorComponents +{ + [_brightnessView setValue:colorComponents.brightness]; + UIColor *tmp = [UIColor colorWithHue:colorComponents.hue saturation:colorComponents.saturation brightness:1.0f alpha:1.0f]; + [_brightnessView setColors:@[(id)[UIColor blackColor].CGColor, (id)tmp.CGColor]]; +} + +- (void)ms_colorDidChangeValue:(MSColorWheelView *)sender +{ + _colorComponents.hue = sender.hue; + _colorComponents.saturation = sender.saturation; + [self.delegate colorView:self didChangeColor:self.color]; + [self reloadData]; +} + +- (void)ms_brightnessDidChangeValue:(MSColorComponentView *)sender +{ + _colorComponents.brightness = sender.value; + [self.delegate colorView:self didChangeColor:self.color]; + [self reloadData]; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSRGBView.h b/Sample Project/TestBed/MSColorPicker/MSRGBView.h new file mode 100644 index 0000000..7ac1d27 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSRGBView.h @@ -0,0 +1,36 @@ +// +// MSRGBView.h +// +// Created by Maksym Shcheglov on 2014-02-16. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +#import "MSColorView.h" + +/** + * The view to edit RGBA color components. + */ +@interface MSRGBView : UIView + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSRGBView.m b/Sample Project/TestBed/MSColorPicker/MSRGBView.m new file mode 100644 index 0000000..54262b1 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSRGBView.m @@ -0,0 +1,225 @@ +// +// MSRGBView.m +// +// Created by Maksym Shcheglov on 2014-02-16. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSRGBView.h" +#import "MSColorComponentView.h" +#import "MSSliderView.h" +#import "MSColorUtils.h" + +extern CGFloat const MSRGBColorComponentMaxValue; + +static CGFloat const MSColorSampleViewHeight = 30.0f; +static CGFloat const MSViewMargin = 20.0f; +static CGFloat const MSSliderViewMargin = 30.0f; +static NSUInteger const MSRGBColorComponentsSize = 3; + +@interface MSRGBView () +{ + @private + + UIView *_colorSample; + NSArray *_colorComponentViews; + RGB _colorComponents; +} + +@end + +@implementation MSRGBView + +@synthesize delegate = _delegate; + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super initWithCoder:aDecoder]; + + if (self) { + [self ms_baseInit]; + } + + return self; +} + +- (void)reloadData +{ + [_colorSample setBackgroundColor:self.color]; + [_colorSample setAccessibilityValue:MSHexStringFromColor(self.color)]; + [self ms_reloadColorComponentViews:_colorComponents]; +} + +- (void)setColor:(UIColor *)color +{ + _colorComponents = MSRGBColorComponents(color); + [self reloadData]; +} + +- (UIColor *)color +{ + return [UIColor colorWithRed:_colorComponents.red green:_colorComponents.green blue:_colorComponents.blue alpha:_colorComponents.alpha]; +} + +#pragma mark - Private methods + +- (void)ms_baseInit +{ + self.accessibilityLabel = @"rgb_view"; + + _colorSample = [[UIView alloc] init]; + _colorSample.accessibilityLabel = @"color_sample"; + _colorSample.layer.borderColor = [UIColor blackColor].CGColor; + _colorSample.layer.borderWidth = .5f; + _colorSample.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_colorSample]; + + NSMutableArray *tmp = [NSMutableArray array]; + NSArray *titles = @[NSLocalizedString(@"Red", ), NSLocalizedString(@"Green", ), NSLocalizedString(@"Blue", )]; + NSArray *maxValues = @[@(MSRGBColorComponentMaxValue), @(MSRGBColorComponentMaxValue), @(MSRGBColorComponentMaxValue)]; + + for (NSUInteger i = 0; i < MSRGBColorComponentsSize; ++i) { + UIControl *colorComponentView = [self ms_colorComponentViewWithTitle:titles[i] tag:i maxValue:[maxValues[i] floatValue]]; + [self addSubview:colorComponentView]; + [colorComponentView addTarget:self action:@selector(ms_colorComponentDidChangeValue:) forControlEvents:UIControlEventValueChanged]; + [tmp addObject:colorComponentView]; + } + + _colorComponentViews = [tmp copy]; + [self ms_installConstraints]; +} + +- (IBAction)ms_colorComponentDidChangeValue:(MSColorComponentView *)sender +{ + [self ms_setColorComponentValue:sender.value / sender.maximumValue atIndex:sender.tag]; + [self.delegate colorView:self didChangeColor:self.color]; + [self reloadData]; +} + +- (void)ms_setColorComponentValue:(CGFloat)value atIndex:(NSUInteger)index +{ + switch (index) { + case 0: + _colorComponents.red = value; + break; + + case 1: + _colorComponents.green = value; + break; + + case 2: + _colorComponents.blue = value; + break; + + default: + _colorComponents.alpha = value; + break; + } +} + +- (UIControl *)ms_colorComponentViewWithTitle:(NSString *)title tag:(NSUInteger)tag maxValue:(CGFloat)maxValue +{ + MSColorComponentView *colorComponentView = [[MSColorComponentView alloc] init]; + + colorComponentView.title = title; + colorComponentView.translatesAutoresizingMaskIntoConstraints = NO; + colorComponentView.tag = tag; + colorComponentView.maximumValue = maxValue; + return colorComponentView; +} + +- (void)ms_installConstraints +{ + NSDictionary *metrics = @{ @"margin": @(MSViewMargin), + @"height": @(MSColorSampleViewHeight), + @"slider_margin": @(MSSliderViewMargin) }; + + __block NSDictionary *views = NSDictionaryOfVariableBindings(_colorSample); + + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[_colorSample]-margin-|" options:0 metrics:metrics views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-margin-[_colorSample(height)]" options:0 metrics:metrics views:views]]; + + __block UIView *previousView = _colorSample; + [_colorComponentViews enumerateObjectsUsingBlock:^(UIView *colorComponentView, NSUInteger idx, BOOL *stop) { + views = NSDictionaryOfVariableBindings(previousView, colorComponentView); + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[colorComponentView]-margin-|" + options:0 + metrics:metrics + views:views]]; + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previousView]-slider_margin-[colorComponentView]" + options:0 + metrics:metrics + views:views]]; + previousView = colorComponentView; + }]; + views = NSDictionaryOfVariableBindings(previousView); + [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[previousView]-(>=margin)-|" options:0 metrics:metrics views:views]]; +} + +- (NSArray *)ms_colorComponentsWithRGB:(RGB)rgb +{ + return @[@(rgb.red), @(rgb.green), @(rgb.blue), @(rgb.alpha)]; +} + +- (void)ms_reloadColorComponentViews:(RGB)colorComponents +{ + NSArray *components = [self ms_colorComponentsWithRGB:colorComponents]; + + [_colorComponentViews enumerateObjectsUsingBlock:^(MSColorComponentView *colorComponentView, NSUInteger idx, BOOL *stop) { + [colorComponentView setColors:[self ms_colorsWithColorComponents:components + currentColorIndex:colorComponentView.tag]]; + colorComponentView.value = [components[idx] floatValue] * colorComponentView.maximumValue; + }]; +} + +- (NSArray *)ms_colorsWithColorComponents:(NSArray *)colorComponents currentColorIndex:(NSUInteger)colorIndex +{ + CGFloat currentColorValue = [colorComponents[colorIndex] floatValue]; + CGFloat colors[12]; + + for (NSUInteger i = 0; i < MSRGBColorComponentsSize; i++) { + colors[i] = [colorComponents[i] floatValue]; + colors[i + 4] = [colorComponents[i] floatValue]; + colors[i + 8] = [colorComponents[i] floatValue]; + } + + colors[colorIndex] = 0; + colors[colorIndex + 4] = currentColorValue; + colors[colorIndex + 8] = 1.0; + UIColor *start = [UIColor colorWithRed:colors[0] green:colors[1] blue:colors[2] alpha:1.0f]; + UIColor *middle = [UIColor colorWithRed:colors[4] green:colors[5] blue:colors[6] alpha:1.0f]; + UIColor *end = [UIColor colorWithRed:colors[8] green:colors[9] blue:colors[10] alpha:1.0f]; + return @[(id)start.CGColor, (id)middle.CGColor, (id)end.CGColor]; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSSliderView.h b/Sample Project/TestBed/MSColorPicker/MSSliderView.h new file mode 100644 index 0000000..e30f423 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSSliderView.h @@ -0,0 +1,54 @@ +// +// MSSliderView.h +// +// Created by Maksym Shcheglov on 2014-01-31. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import + +/** + * The slider with a gradient track. + */ +@interface MSSliderView : UIControl + +/** + * The slider's current value. The default value is 0.0. + */ +@property (nonatomic, assign) CGFloat value; +/** + * The minimum value of the slider. The default value is 0.0. + */ +@property (nonatomic, assign) CGFloat minimumValue; +/** + * The maximum value of the slider. The default value is 1.0. + */ +@property (nonatomic, assign) CGFloat maximumValue; +/** + * Sets the array of CGColorRef objects defining the color of each gradient stop on the track. + * The location of each gradient stop is evaluated with formula: i * width_of_the_track / number_of_colors. + * + * @param colors An array of CGColorRef objects. + */ +- (void)setColors:(NSArray *)colors __attribute__((nonnull(1))); + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSSliderView.m b/Sample Project/TestBed/MSColorPicker/MSSliderView.m new file mode 100644 index 0000000..e49a3db --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSSliderView.m @@ -0,0 +1,187 @@ +// +// MSSliderView.m +// +// Created by Maksym Shcheglov on 2014-01-31. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "MSSliderView.h" +#import "MSThumbView.h" +#import "UIControl+HitTestEdgeInsets.h" + +static const CGFloat MSSliderViewHeight = 28.0f; +static const CGFloat MSSliderViewMinWidth = 150.0f; +static const CGFloat MSSliderViewTrackHeight = 3.0f; +static const CGFloat MSThumbViewEdgeInset = -10.0f; + +@interface MSSliderView () { + @private + + MSThumbView *_thumbView; + CAGradientLayer *_trackLayer; +} + +@end + +@implementation MSSliderView + ++ (BOOL)requiresConstraintBasedLayout +{ + return YES; +} + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + + if (self) { + self.accessibilityLabel = @"color_slider"; + + _minimumValue = 0.0f; + _maximumValue = 1.0f; + _value = 0.0f; + + self.layer.delegate = self; + + _trackLayer = [CAGradientLayer layer]; + _trackLayer.cornerRadius = MSSliderViewTrackHeight / 2.0f; + _trackLayer.startPoint = CGPointMake(0.0f, 0.5f); + _trackLayer.endPoint = CGPointMake(1.0f, 0.5f); + [self.layer addSublayer:_trackLayer]; + + _thumbView = [[MSThumbView alloc] init]; + _thumbView.hitTestEdgeInsets = UIEdgeInsetsMake(MSThumbViewEdgeInset, MSThumbViewEdgeInset, MSThumbViewEdgeInset, MSThumbViewEdgeInset); + [_thumbView.gestureRecognizer addTarget:self action:@selector(ms_didPanThumbView:)]; + [self addSubview:_thumbView]; + + __attribute__((objc_precise_lifetime)) id color = (__bridge id)[UIColor blueColor].CGColor; + [self setColors:@[color, color]]; + } + + return self; +} + +- (CGSize)intrinsicContentSize +{ + return CGSizeMake(MSSliderViewMinWidth, MSSliderViewHeight); +} + +- (void)setValue:(CGFloat)value +{ + if (value < _minimumValue) { + _value = _minimumValue; + } else if (value > _maximumValue) { + _value = _maximumValue; + } else { + _value = value; + } + + [self ms_updateThumbPositionWithValue:_value]; +} + +- (void)setColors:(NSArray *)colors +{ + NSParameterAssert(colors); + _trackLayer.colors = colors; + [self ms_updateLocations]; +} + +- (void)layoutSubviews +{ + [self ms_updateThumbPositionWithValue:_value]; + [self ms_updateTrackLayer]; +} + +#pragma mark - UIControl touch tracking events + +- (void)ms_didPanThumbView:(UIPanGestureRecognizer *)gestureRecognizer +{ + if (gestureRecognizer.state != UIGestureRecognizerStateBegan && gestureRecognizer.state != UIGestureRecognizerStateChanged) { + return; + } + + CGPoint translation = [gestureRecognizer translationInView:self]; + [gestureRecognizer setTranslation:CGPointZero inView:self]; + + [self ms_setValueWithTranslation:translation.x]; +} + +- (void)ms_updateTrackLayer +{ + CGFloat height = MSSliderViewHeight; + CGFloat width = CGRectGetWidth(self.bounds); + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + _trackLayer.bounds = CGRectMake(0, 0, width, MSSliderViewTrackHeight); + _trackLayer.position = CGPointMake(CGRectGetWidth(self.bounds) / 2, height / 2); + [CATransaction commit]; +} + +#pragma mark - Private methods + +- (void)ms_setValueWithTranslation:(CGFloat)translation +{ + CGFloat width = CGRectGetWidth(self.bounds) - CGRectGetWidth(_thumbView.bounds); + CGFloat valueRange = (_maximumValue - _minimumValue); + CGFloat value = _value + valueRange * translation / width; + + [self setValue:value]; + [self sendActionsForControlEvents:UIControlEventValueChanged]; +} + +- (void)ms_updateLocations +{ + NSUInteger size = [_trackLayer.colors count]; + + if (size == [_trackLayer.locations count]) { + return; + } + + CGFloat step = 1.0f / (size - 1); + NSMutableArray *locations = [NSMutableArray array]; + [locations addObject:@(0.0f)]; + + for (NSUInteger i = 1; i < size - 1; ++i) { + [locations addObject:@(i * step)]; + } + + [locations addObject:@(1.0f)]; + _trackLayer.locations = [locations copy]; +} + +- (void)ms_updateThumbPositionWithValue:(CGFloat)value +{ + CGFloat thumbWidth = CGRectGetWidth(_thumbView.bounds); + CGFloat thumbHeight = CGRectGetHeight(_thumbView.bounds); + CGFloat width = CGRectGetWidth(self.bounds) - thumbWidth; + + if (width <= 0) { + return; + } + + CGFloat percentage = (value - _minimumValue) / (_maximumValue - _minimumValue); + CGFloat position = width * percentage; + _thumbView.frame = CGRectMake(position, 0, thumbWidth, thumbHeight); +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSThumbView.h b/Sample Project/TestBed/MSColorPicker/MSThumbView.h new file mode 100644 index 0000000..235ceaf --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSThumbView.h @@ -0,0 +1,14 @@ +// +// MSThumbView.h +// +// Created by Maksym Shcheglov on 2016-05-25. +// Copyright (c) 2016 Maksym Shcheglov. +// License: http://opensource.org/licenses/MIT +// + +#import +#import + +@interface MSThumbView : UIControl +@property (nonatomic, strong, readonly) UIGestureRecognizer *gestureRecognizer; +@end diff --git a/Sample Project/TestBed/MSColorPicker/MSThumbView.m b/Sample Project/TestBed/MSColorPicker/MSThumbView.m new file mode 100644 index 0000000..88c05b4 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/MSThumbView.m @@ -0,0 +1,56 @@ +// +// MSThumbView.m +// +// Created by Maksym Shcheglov on 2016-05-25. +// Copyright (c) 2016 Maksym Shcheglov. +// License: http://opensource.org/licenses/MIT +// + +#import "MSThumbView.h" + +static const CGFloat MSSliderViewThumbDimension = 28.0f; + +@interface MSThumbView () +@property (nonatomic, strong) CALayer *thumbLayer; +@property (nonatomic, strong) UIGestureRecognizer *gestureRecognizer; +@end + +@implementation MSThumbView + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:CGRectMake(frame.origin.x, frame.origin.y, MSSliderViewThumbDimension, MSSliderViewThumbDimension)]; + + if (self) { + self.thumbLayer = [CALayer layer]; + + self.thumbLayer.borderColor = [[UIColor lightGrayColor] colorWithAlphaComponent:.4].CGColor; + self.thumbLayer.borderWidth = .5; + self.thumbLayer.cornerRadius = MSSliderViewThumbDimension / 2; + self.thumbLayer.backgroundColor = [UIColor whiteColor].CGColor; + self.thumbLayer.shadowColor = [UIColor blackColor].CGColor; + self.thumbLayer.shadowOffset = CGSizeMake(0.0, 3.0); + self.thumbLayer.shadowRadius = 2; + self.thumbLayer.shadowOpacity = 0.3f; + [self.layer addSublayer:self.thumbLayer]; + self.gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:nil action:nil]; + [self addGestureRecognizer:self.gestureRecognizer]; + } + + return self; +} + +- (void)layoutSublayersOfLayer:(CALayer *)layer +{ + if (layer != self.layer) { + return; + } + + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + self.thumbLayer.bounds = CGRectMake(0, 0, MSSliderViewThumbDimension, MSSliderViewThumbDimension); + self.thumbLayer.position = CGPointMake(MSSliderViewThumbDimension / 2, MSSliderViewThumbDimension / 2); + [CATransaction commit]; +} + +@end diff --git a/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.h b/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.h new file mode 100644 index 0000000..f02e571 --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.h @@ -0,0 +1,37 @@ +// +// UIControl+HitTestEdgeInsets.h +// +// Created by Maksym Shcheglov on 18/05/16. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import + +@interface UIControl (HitTestEdgeInsets) + +/** + * Edge inset values are applied to a view bounds to shrink or expand the touchable area. + */ +@property (nonatomic, assign) UIEdgeInsets hitTestEdgeInsets; + +@end diff --git a/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.m b/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.m new file mode 100644 index 0000000..085c23f --- /dev/null +++ b/Sample Project/TestBed/MSColorPicker/UIControl+HitTestEdgeInsets.m @@ -0,0 +1,65 @@ +// +// UIControl+HitTestEdgeInsets.m +// +// Created by Maksym Shcheglov on 18/05/16. +// +// The MIT License (MIT) +// Copyright (c) 2015 Maksym Shcheglov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + + +#import "UIControl+HitTestEdgeInsets.h" + +#import + +@implementation UIControl (HitTestEdgeInsets) + +- (void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets +{ + NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)]; + + objc_setAssociatedObject(self, @selector(hitTestEdgeInsets), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (UIEdgeInsets)hitTestEdgeInsets +{ + NSValue *value = objc_getAssociatedObject(self, @selector(hitTestEdgeInsets)); + + if (value) { + UIEdgeInsets edgeInsets; + [value getValue:&edgeInsets]; + return edgeInsets; + } + + return UIEdgeInsetsZero; +} + +- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event +{ + if (UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) || !self.enabled || self.hidden || !self.userInteractionEnabled || self.alpha <= 0) return [super pointInside:point withEvent:event]; + + CGRect relativeFrame = self.bounds; + CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets); + + return CGRectContainsPoint(hitFrame, point); +} + +@end diff --git a/Sample Project/TestBed/MasterViewController.m b/Sample Project/TestBed/MasterViewController.m index fd78e40..858f449 100644 --- a/Sample Project/TestBed/MasterViewController.m +++ b/Sample Project/TestBed/MasterViewController.m @@ -9,6 +9,7 @@ #import "MasterViewController.h" #import "DetailViewController.h" #import "ARFontPickerViewController.h" +#import "MSColorSelectionViewController.h" //some convenience extensions for setting and reading @interface UITextField (Numbers) @@ -17,6 +18,14 @@ @interface UITextField (Numbers) @end +@interface MasterViewController () + +@property (assign) BEMLineAnimation saveAnimationSetting; +@property (strong, nonatomic) UIColor * saveColorSetting; +@property (strong, nonatomic) NSString * currentColorKey; +@property (strong, nonatomic) UIView * currentColorChip; +@end + @implementation UITextField (Numbers) -(void) setFloatValue:(CGFloat) num { @@ -142,6 +151,27 @@ @interface MasterViewController () ] - - [defaults setInteger: myGraph.touchReportFingersRequired forKey: @"touchReportFingersRequired"]; - [defaults setObject: myGraph.formatStringForValues forKey: @"formatStringForValues"]; -} -*/ - -- (void)decodeRestorableStateWithCoder:(NSCoder *)coder { - - [super decodeRestorableStateWithCoder:coder]; - - #define RestoreProperty(property, type) \ - if ([coder containsValueForKey:@#property]) { \ - self.myGraph.property = [coder decode ## type ##ForKey:@#property ]; \ - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion" - - RestoreProperty (labelFont, Object); - RestoreProperty (animationGraphEntranceTime, Double); - RestoreProperty (animationGraphStyle, Integer); - - RestoreProperty (colorBottom, Object); - RestoreProperty (colorTop, Object); - RestoreProperty (colorLine, Object); - RestoreProperty (colorReferenceLines, Object); - RestoreProperty (colorPoint, Object); - RestoreProperty (colorTouchInputLine, Object); - RestoreProperty (colorXaxisLabel, Object); - RestoreProperty (colorYaxisLabel, Object); - RestoreProperty (colorBackgroundYaxis, Object); - RestoreProperty (colorBackgroundXaxis, Object); - RestoreProperty (colorBackgroundPopUplabel, Object); - RestoreProperty (noDataLabelColor, Object); - RestoreProperty(noDataLabelFont, Object); - //Can't handle: gradientBottom, gradientTop, gradientLine - RestoreProperty (gradientLineDirection, Float); - - RestoreProperty (alphaBottom, Double); - RestoreProperty (alphaTop, Double); - RestoreProperty (alphaLine, Double); - RestoreProperty (alphaTouchInputLine, Double); - RestoreProperty (alphaBackgroundXaxis, Double); - RestoreProperty (alphaBackgroundYaxis, Double); - - RestoreProperty (widthLine, Double); - RestoreProperty (widthReferenceLines, Double); - RestoreProperty (sizePoint, Double); - RestoreProperty (widthTouchInputLine, Double); - - RestoreProperty (enableTouchReport, Bool); - RestoreProperty (enablePopUpReport, Bool); - RestoreProperty (enableBezierCurve, Bool); - RestoreProperty (enableXAxisLabel, Bool); - RestoreProperty (enableYAxisLabel, Bool); - RestoreProperty (autoScaleYAxis, Bool); - RestoreProperty (alwaysDisplayDots, Bool); - RestoreProperty (alwaysDisplayPopUpLabels, Bool); - RestoreProperty (enableReferenceXAxisLines, Bool); - RestoreProperty (enableReferenceYAxisLines, Bool); - RestoreProperty (enableReferenceAxisFrame, Bool); - RestoreProperty (enableLeftReferenceAxisFrameLine, Bool); - RestoreProperty (enableBottomReferenceAxisFrameLine, Bool); - RestoreProperty (enableTopReferenceAxisFrameLine, Bool); - RestoreProperty (enableRightReferenceAxisFrameLine, Bool); - [self updateReferenceAxisFrame:self.myGraph.enableReferenceAxisFrame]; - RestoreProperty (interpolateNullValues, Bool); - RestoreProperty (displayDotsOnly, Bool); - RestoreProperty (displayDotsWhileAnimating, Bool); - - RestoreProperty (touchReportFingersRequired, Int); - RestoreProperty (formatStringForValues, Object); - RestoreProperty (lineDashPatternForReferenceXAxisLines, Object); - RestoreProperty (lineDashPatternForReferenceYAxisLines, Object); - - if ([coder containsValueForKey:@"averageLine.enableAverageLine" ]) { - self.myGraph.averageLine = [[BEMAverageLine alloc] initWithCoder:coder]; - RestoreProperty (averageLine.enableAverageLine, Bool); - RestoreProperty (averageLine.color, Object); - RestoreProperty (averageLine.yValue, Double); - RestoreProperty (averageLine.alpha, Double); - RestoreProperty (averageLine.width, Double); - RestoreProperty (averageLine.dashPattern, Object); - RestoreProperty (averageLine.title, Object); - } -#define RestoreVCProperty(property, type) \ -if ([coder containsValueForKey:@#property]) { \ -self.detailViewController.property = [coder decode ## type ##ForKey:@#property ]; \ -} - RestoreVCProperty(popUpText, Object); - RestoreVCProperty(popUpPrefix, Object); - RestoreVCProperty(popUpSuffix, Object); - RestoreVCProperty(testAlwaysDisplayPopup, Bool); - RestoreVCProperty(maxValue, Double); - RestoreVCProperty(minValue, Double); - RestoreVCProperty(noDataLabel, Bool); - RestoreVCProperty(noDataText, Object); - RestoreVCProperty(staticPaddingValue, Double); - RestoreVCProperty(provideCustomView, Bool); - RestoreVCProperty(numberOfGapsBetweenLabels, Integer); - RestoreVCProperty(baseIndexForXAxis, Integer); - RestoreVCProperty(incrementIndexForXAxis, Integer); - RestoreVCProperty(provideIncrementPositionsForXAxis, Bool); - RestoreVCProperty(numberOfYAxisLabels, Integer); - RestoreVCProperty(yAxisPrefix, Object); - RestoreVCProperty(yAxisSuffix, Object); - RestoreVCProperty(baseValueForYAxis, Double); - RestoreVCProperty(incrementValueForYAxis, Double); -} -#pragma clang diagnostic pop - -- (void)encodeRestorableStateWithCoder:(NSCoder *)coder { - [super encodeRestorableStateWithCoder:coder]; - -#define EncodeProperty(property, type) [coder encode ## type: self.myGraph.property forKey:@#property] - - if (self.myGraph.labelFont && ![self.myGraph.labelFont isEqual:[UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]] ) { - EncodeProperty (labelFont, Object); - } - EncodeProperty (animationGraphEntranceTime, Float); - EncodeProperty (animationGraphStyle, Integer); - - EncodeProperty (colorBottom, Object); - EncodeProperty (colorTop, Object); - EncodeProperty (colorLine, Object); - EncodeProperty (colorReferenceLines, Object); - EncodeProperty (colorPoint, Object); - EncodeProperty (colorTouchInputLine, Object); - EncodeProperty (colorXaxisLabel, Object); - EncodeProperty (colorYaxisLabel, Object); - EncodeProperty (colorBackgroundYaxis, Object); - EncodeProperty (colorBackgroundXaxis, Object); - EncodeProperty (colorBackgroundPopUplabel, Object); - EncodeProperty (noDataLabelColor, Object); - EncodeProperty(noDataLabelFont, Object); - //Can't handle: gradientBottom, gradientTop, gradientLine - EncodeProperty (gradientLineDirection, Float); - - EncodeProperty (alphaBottom, Float); - EncodeProperty (alphaTop, Float); - EncodeProperty (alphaLine, Float); - EncodeProperty (alphaTouchInputLine, Float); - EncodeProperty (alphaBackgroundXaxis, Float); - EncodeProperty (alphaBackgroundYaxis, Float); - - EncodeProperty (widthLine, Float); - EncodeProperty (widthReferenceLines, Float); - EncodeProperty (sizePoint, Float); - EncodeProperty (widthTouchInputLine, Float); - - EncodeProperty (enableTouchReport, Bool); - EncodeProperty (enablePopUpReport, Bool); - EncodeProperty (enableBezierCurve, Bool); - EncodeProperty (enableXAxisLabel, Bool); - EncodeProperty (enableYAxisLabel, Bool); - EncodeProperty (autoScaleYAxis, Bool); - EncodeProperty (alwaysDisplayDots, Bool); - EncodeProperty (alwaysDisplayPopUpLabels, Bool); - EncodeProperty (enableReferenceXAxisLines, Bool); - EncodeProperty (enableReferenceYAxisLines, Bool); - EncodeProperty (enableReferenceAxisFrame, Bool); - EncodeProperty (enableLeftReferenceAxisFrameLine, Bool); - EncodeProperty (enableBottomReferenceAxisFrameLine, Bool); - EncodeProperty (enableTopReferenceAxisFrameLine, Bool); - EncodeProperty (enableRightReferenceAxisFrameLine, Bool); - EncodeProperty (interpolateNullValues, Bool); - EncodeProperty (displayDotsOnly, Bool); - EncodeProperty (displayDotsWhileAnimating, Bool); - - EncodeProperty (touchReportFingersRequired, Integer); - EncodeProperty (formatStringForValues, Object); - EncodeProperty (lineDashPatternForReferenceXAxisLines, Object); - EncodeProperty (lineDashPatternForReferenceYAxisLines, Object); - - if (self.myGraph.averageLine) { - EncodeProperty (averageLine.enableAverageLine, Bool); - EncodeProperty (averageLine.color, Object); - EncodeProperty (averageLine.yValue, Float); - EncodeProperty (averageLine.alpha, Float); - EncodeProperty (averageLine.width, Float); - EncodeProperty (averageLine.dashPattern, Object); - EncodeProperty (averageLine.title, Object); - } - -#define EncodeVCProperty(property, type) [coder encode ## type: self.detailViewController.property forKey:@#property] - - EncodeVCProperty(popUpText, Object); - EncodeVCProperty(popUpPrefix, Object); - EncodeVCProperty(popUpSuffix, Object); - EncodeVCProperty(testAlwaysDisplayPopup, Bool); - EncodeVCProperty(maxValue, Float); - EncodeVCProperty(minValue, Float); - EncodeVCProperty(noDataLabel, Bool); - EncodeVCProperty(noDataText, Object); - EncodeVCProperty(staticPaddingValue, Float); - EncodeVCProperty(provideCustomView, Bool); - EncodeVCProperty(numberOfGapsBetweenLabels, Integer); - EncodeVCProperty(baseIndexForXAxis, Integer); - EncodeVCProperty(incrementIndexForXAxis, Integer); - EncodeVCProperty(provideIncrementPositionsForXAxis, Bool); - EncodeVCProperty(numberOfYAxisLabels, Integer); - EncodeVCProperty(yAxisPrefix, Object); - EncodeVCProperty(yAxisSuffix, Object); - EncodeVCProperty(baseValueForYAxis, Float); - EncodeVCProperty(incrementValueForYAxis, Float); - -} - -//- (void)saveGraphView{ -// NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.myGraph]; -// [[NSUserDefaults standardUserDefaults] setObject:encodedObject forKey:@"graphView"]; -//} -// -//- (BEMSimpleLineGraphView *)loadGraphView { -// NSData *encodedObject = [[NSUserDefaults standardUserDefaults] objectForKey:@"graphView"]; -// BEMSimpleLineGraphView *graphView = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject]; -// return graphView; -//} -// CGGradientRef createGradient () { CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB(); size_t num_locations = 2; @@ -510,25 +200,17 @@ - (void)viewDidLoad { self.navigationItem.leftBarButtonItem = self.editButtonItem; self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; +} + +-(void) viewWillAppear:(BOOL)animated { + [super viewDidAppear:animated]; [self.detailViewController loadViewIfNeeded]; self.myGraph = self.detailViewController.myGraph; - if (!self.myGraph.averageLine) { // Draw an average line - self.myGraph.averageLine.enableAverageLine = YES; - self.myGraph.averageLine.alpha = 0.6; - self.myGraph.averageLine.color = [UIColor darkGrayColor]; - self.myGraph.averageLine.width = 2.5; - self.myGraph.averageLine.dashPattern = @[@(2),@(2)]; - self.myGraph.averageLine.title = @"Average"; - } - // Apply the gradient to the bottom portion of the graph - CGGradientRef gradient = createGradient(); - self.myGraph.gradientBottom = gradient; - CGGradientRelease(gradient); - -// [[NSNotificationCenter defaultCenter] addObserver:self -// selector:@selector(saveGraphView) -// name:UIApplicationDidEnterBackgroundNotification -// object:nil]; + [self restoreUI]; +} + +-(void) restoreUI { + self.widthLine.floatValue = self.myGraph.widthLine; self.staticPaddingField.floatValue = self.detailViewController.staticPaddingValue; self.bezierSwitch.on = self.myGraph.enableBezierCurve; @@ -583,11 +265,33 @@ - (void)viewDidLoad { self.touchReportSwitch.on = self.myGraph.enableTouchReport; self.widthTouchInputLineField.floatValue = self.myGraph.widthTouchInputLine; - // self.fontNameButton = self.myGraph.xx; - // self.fontSizeField = self.myGraph.xx; + self.fontNameButton.titleLabel.text = self.myGraph.labelFont.fontName; + self.fontSizeField.floatValue = self.myGraph.labelFont.pointSize; self.numberFormatField.text = self.myGraph.formatStringForValues; + self.colorTopChip.backgroundColor = self.myGraph.colorTop; + self.colorBottomChip.backgroundColor = self.myGraph.colorBottom; + self.gradientTopSwitch.on = self.myGraph.gradientTop != nil; + self.gradientBottomSwitch.on = self.myGraph.gradientBottom != nil; + self.gradientHorizSwitch.on = self.myGraph.gradientLineDirection == BEMLineGradientDirectionHorizontal; + self.gradientLineSwitch.on = self.myGraph.gradientLine != nil; + + self.colorLineChip.backgroundColor = self.myGraph.colorLine; + self.colorPointChip.backgroundColor = self.myGraph.colorPoint; + self.colorXaxisLabelChip.backgroundColor = self.myGraph.colorXaxisLabel; + self.colorBackgroundXaxisChip.backgroundColor = self.myGraph.colorBackgroundXaxis ?: self.myGraph.colorBottom; + self.colorTouchInputLineChip.backgroundColor = self.myGraph.colorTouchInputLine; + self.colorYaxisLabelChip.backgroundColor = self.myGraph.colorYaxisLabel; + self.colorBackgroundYaxisChip.backgroundColor = self.myGraph.colorBackgroundYaxis ?: self.myGraph.colorTop; + self.colorBackgroundPopUpLabelChip.backgroundColor = self.myGraph.colorBackgroundPopUplabel; + + self.alphaTopField.floatValue= self.myGraph.alphaTop; + self.alphaBottomField.floatValue = self.myGraph.alphaBottom; + self.alphaLineField.floatValue = self.myGraph.alphaLine; + self.alphaTouchInputLineField.floatValue = self.myGraph.alphaTouchInputLine; + self.alphaBackgroundXaxisField.floatValue = self.myGraph.alphaBackgroundXaxis; + self.alphaBackgroundYaxisField.floatValue = self.myGraph.alphaBackgroundYaxis; } @@ -595,17 +299,9 @@ - (void)viewDidLoad { touchReportFingersRequired, autoScaleYAxis - Colors/Gradients - - averageLine: Color/alpha/dashPashpattern - Top: Color/Alpha/Gradient - Line: Color/alpha/gradient/gradientLineDirection - ReferenceLines - Point: color - touchInputLine: color/alopha - XAxis: color/colorBackground, alphaBackground, lineDashPattern - Yaxis: color/colorBackground, alphaBackground, lineDashPattern - noDataLabel: color/Font + Dashpatterns for averageLine, XAxis, Yaxis + + Gradient choices */ @@ -634,6 +330,9 @@ - (IBAction)interpolateNullValues:(UISwitch *)sender { } #pragma mark Axes and Reference Lines +-(NSUInteger) getValue:(NSString *) text { + return (text.length > 0 && text.integerValue >= 0) ? text.integerValue : NSNotFound; +} - (IBAction)enableXAxisLabel:(UISwitch *)sender { self.myGraph.enableXAxisLabel = sender.on; @@ -641,18 +340,19 @@ - (IBAction)enableXAxisLabel:(UISwitch *)sender { } - (IBAction)numberOfGapsBetweenLabelDidChange:(UITextField *)sender { - self.detailViewController.numberOfGapsBetweenLabels = sender.text.doubleValue; + self.detailViewController.numberOfGapsBetweenLabels = [self getValue:sender.text]; [self.myGraph reloadGraph]; } - (IBAction)baseIndexForXAxisDidChange:(UITextField *)sender { - self.detailViewController.baseIndexForXAxis = sender.text.doubleValue; + self.detailViewController.baseIndexForXAxis = [self getValue:sender.text]; [self.myGraph reloadGraph]; } - (IBAction)incrementIndexForXAxisDidChange:(UITextField *)sender { - self.detailViewController.incrementIndexForXAxis = sender.text.doubleValue; + self.detailViewController.incrementIndexForXAxis = [self getValue:sender.text]; [self.myGraph reloadGraph]; } + - (IBAction)enableArrayOfIndicesForXAxis:(UISwitch *)sender { self.detailViewController.provideIncrementPositionsForXAxis = sender.on; [self.myGraph reloadGraph]; @@ -683,10 +383,7 @@ - (IBAction)maxValueDidChange:(UITextField *)sender { } - (IBAction)numberofYAxisDidChange:(UITextField *)sender { - if (sender.text.integerValue <= 0) { - sender.text = @"1.0"; - } - self.detailViewController.numberOfYAxisLabels = sender.text.integerValue; + self.detailViewController.numberOfYAxisLabels = [self getValue:sender.text]; [self.myGraph reloadGraph]; } @@ -823,7 +520,7 @@ - (IBAction)enableTestDisplayPopups:(UISwitch *)sender { } - (IBAction)labelTextDidChange:(UITextField *)sender { - self.detailViewController.popUpText = sender.text; + self.detailViewController.popUpText = [self checkUsersFormatString:sender]; [self.myGraph reloadGraph]; } @@ -957,12 +654,149 @@ - (void)fontPickerViewController:(ARFontPickerViewController *)fontPicker didSel - (IBAction)fontSizeFieldChanged:(UITextField *)sender { [self updateFont:nil]; - } - (IBAction)numberFormatChanged:(UITextField *)sender { + self.myGraph.formatStringForValues = [self checkUsersFormatString:sender]; + [self.myGraph reloadGraph]; +} +-(NSString *) checkUsersFormatString: (UITextField *) sender { + //there are many ways to crash this (more than one format), but this is most obvious NSString * newFormat = sender.text ?: @""; - self.myGraph.formatStringForValues = newFormat; + if ([newFormat containsString:@"%@"]) { + //prevent crash + NSLog(@"%%@ not allowed in numeric format strings"); + newFormat = [newFormat stringByReplacingOccurrencesOfString:@"%@" withString:@"%%@"]; + sender.text = newFormat; + } + return newFormat; +} + +-(IBAction) alphaTopFieldChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaTop = newAlpha; + [self.myGraph reloadGraph]; + } +} + +-(IBAction) alphaBottomFieldChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaBottom = newAlpha; + [self.myGraph reloadGraph]; + } +} + +-(IBAction) alphaLineFieldChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaLine = newAlpha; + [self.myGraph reloadGraph]; + } +} + +-(IBAction) alphaTouchInputFieldChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaTouchInputLine = newAlpha; + [self.myGraph reloadGraph]; + } +} + +-(IBAction) alphaBackgroundXaxisChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaBackgroundXaxis = newAlpha; + [self.myGraph reloadGraph]; + } +} + +-(IBAction) alphaBackgroundYaxisChanged:(UITextField *) sender { + float newAlpha = sender.floatValue; + if (newAlpha >= 0 && newAlpha <= 1.0) { + self.myGraph.alphaBackgroundYaxis = newAlpha; + [self.myGraph reloadGraph]; + } +} + +#pragma Color section +-(void) didChangeColor: (UIColor *) color { + if (![color isEqual:self.currentColorChip.backgroundColor]) { + self.currentColorChip.backgroundColor = color; + [self.myGraph setValue: color forKey: self.currentColorKey]; + [self.myGraph reloadGraph]; + } + +} +- (void)colorViewController:(MSColorSelectionViewController *)colorViewCntroller didChangeColor:(UIColor *)color { + [self didChangeColor:color]; +} + +-(void) saveColor:(id) sender { + self.myGraph.animationGraphStyle = self.saveAnimationSetting; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)restoreColor:(id)sender { + if (self.saveColorSetting) { + [self didChangeColor:self.saveColorSetting]; + } else { + [self.myGraph setValue: nil forKey: self.currentColorKey]; + + if ([self.currentColorKey isEqualToString:@"colorBackgroundYaxis"]) { + self.currentColorChip.backgroundColor = self.myGraph.colorTop; + } else if ([self.currentColorKey isEqualToString:@"colorBackgroundXaxis"]) { + self.currentColorChip.backgroundColor = self.myGraph.colorBottom; + } + + } + self.myGraph.animationGraphStyle = self.saveAnimationSetting; + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)popoverPresentationControllerDidDismissPopover:(UIPopoverPresentationController *)popoverPresentationController { + [self restoreColor:nil]; +} + +- (IBAction)enableGradientTop:(UISwitch *)sender { + if (sender.on) { + CGGradientRef gradient = createGradient(); + self.myGraph.gradientTop = gradient; + CGGradientRelease(gradient); + } else { + self.myGraph.gradientTop = nil; + } + + [self.myGraph reloadGraph]; +} + +- (IBAction)enableGradientBottom:(UISwitch *)sender { + if (sender.on) { + CGGradientRef gradient = createGradient(); + self.myGraph.gradientBottom = gradient; + CGGradientRelease(gradient); + } else { + self.myGraph.gradientBottom = nil; + } + + [self.myGraph reloadGraph]; +} + +- (IBAction)enableGradientLine:(UISwitch *)sender { + if (sender.on) { + CGGradientRef gradient = createGradient(); + self.myGraph.gradientLine = gradient; + CGGradientRelease(gradient); + } else { + self.myGraph.gradientLine = nil; + } + + [self.myGraph reloadGraph]; +} + +- (IBAction)enableGradientHoriz:(UISwitch *)sender { + self.myGraph.gradientLineDirection = sender.on ? BEMLineGradientDirectionVertical : BEMLineGradientDirectionHorizontal; [self.myGraph reloadGraph]; } @@ -976,9 +810,64 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { } else if ([[segue identifier] isEqualToString:@"FontPicker"]) { ARFontPickerViewController * controller = (ARFontPickerViewController*) [segue destinationViewController]; controller.delegate = self; + } else if ([segue.identifier hasPrefix:@"color"]) { + + //set up color selector + UINavigationController *destNav = segue.destinationViewController; + destNav.popoverPresentationController.delegate = self; +// CGRect cellFrame = [self.view convertRect:((UIView *)sender).bounds fromView:sender]; + destNav.popoverPresentationController.sourceView = ((UIView *)sender) ; + destNav.popoverPresentationController.sourceRect = ((UIView *)sender).bounds ; + destNav.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionLeft; + destNav.preferredContentSize = [[destNav visibleViewController].view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; + MSColorSelectionViewController *colorSelectionController = (MSColorSelectionViewController *)destNav.visibleViewController; + colorSelectionController.delegate = self; + + UIBarButtonItem *doneBtn = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Save", ) style:UIBarButtonItemStyleDone target:self action:@selector(saveColor:)]; + colorSelectionController.navigationItem.rightBarButtonItem = doneBtn; + UIBarButtonItem *cancelBtn = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"Cancel", ) style:UIBarButtonItemStyleDone target:self action:@selector(restoreColor:)]; + colorSelectionController.navigationItem.leftBarButtonItem = cancelBtn; + + + //remember stuff from sender tableviewCell + if ([sender isKindOfClass:[UITableViewCell class]]) { + NSArray * subViews = [[(UITableViewCell *) sender contentView] subviews]; + for (UIView * subView in subViews) { + if (subView.tag == 12343) { + self.currentColorChip = subView; + break; + } + } + } + self.currentColorKey = segue.identifier; + + NSAssert(self.currentColorKey != nil && self.currentColorChip != nil, @"View Structural problem"); + //add numbers to color selector + //temp turn off animation in graph + //remember original color + //set color to last color set if any to copy from one to another + self.saveColorSetting = (UIColor *) [self.myGraph valueForKey:self.currentColorKey]; + if (!self.saveColorSetting) { + //value is not currently set + if ([self.currentColorKey isEqualToString:@"colorBackgroundYaxis"]) { + self.myGraph.colorBackgroundYaxis = self.myGraph.colorTop; + self.currentColorChip.backgroundColor = self.myGraph.colorTop; + } else if ([self.currentColorKey isEqualToString:@"colorBackgroundXaxis"]) { + self.myGraph.colorBackgroundXaxis = self.myGraph.colorBottom; + self.currentColorChip.backgroundColor = self.myGraph.colorBottom; + } else { + self.saveColorSetting = [UIColor blueColor]; + [self didChangeColor:self.saveColorSetting]; + } + } + self.saveAnimationSetting = self.myGraph.animationGraphStyle; + self.myGraph.animationGraphStyle = BEMLineAnimationNone; + + colorSelectionController.color = (UIColor * _Nonnull)(self.saveColorSetting ) ?: self.currentColorChip.backgroundColor; } -} + +} #pragma mark - Table View - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { @@ -1000,6 +889,9 @@ - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *) // Return NO if you do not want the specified item to be editable. return NO; } +-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:NO]; +} #pragma mark TextDelegate From 78c8cac4e8dddcd46f9f437624f9bb94deb0a4d1 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sat, 25 Mar 2017 18:04:38 -0700 Subject: [PATCH 05/15] =?UTF-8?q?Fix=20assorted=20bugs=20in=20BEMSimpleLin?= =?UTF-8?q?eGraph,=20especially=20null-data=20related=20AverageLine:=20?= =?UTF-8?q?=E2=80=A2Color=20should=20be=20picked=20up=20from=20line=20if?= =?UTF-8?q?=20not=20set=20(default=20nil)=20Typo=20in=20encoding=20macro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Line: If null data and interpolation on, then extrapolate for beginning/ending nulls. Bezier curve should be used even when only two points. If interpolation off, then bezier line should be interrupted by gaps (but not top/bottom). Avoid infinity result in midPoint calculation SimpleLineGraphView: Support restoration during startup. NoDataLabel color should not default from Line (which defaults to white, same as background) If null value, ensure corresponding Dot isn't left on chart If label isn't used after initially being created, ensure it's removed from view If neither X nor Y reference lines, set line's enableRefLines to NO (although default, might have been previously YES If Xaxis background is defaulting to colorBottom, then also use alphaBottom to match; same for Yaxis and colorTop. Avoid possible infinite loop if delegate gives a zero incrementIndex for x axis Fix one-pixel gap between yaxis and graph Remove spurious NSLogs --- Classes/BEMAverageLine.m | 3 +- Classes/BEMLine.m | 85 +++++++++++++++------- Classes/BEMSimpleLineGraphView.m | 118 ++++++++++++++++++++----------- 3 files changed, 137 insertions(+), 69 deletions(-) diff --git a/Classes/BEMAverageLine.m b/Classes/BEMAverageLine.m index a4cbfa2..3aa8c31 100644 --- a/Classes/BEMAverageLine.m +++ b/Classes/BEMAverageLine.m @@ -14,7 +14,6 @@ - (instancetype)init { self = [super init]; if (self) { _enableAverageLine = NO; - _color = [UIColor whiteColor]; _alpha = 1.0; _width = 3.0; _yValue = NAN; @@ -49,7 +48,7 @@ - (instancetype) initWithCoder:(NSCoder *)coder { - (void) encodeWithCoder: (NSCoder *)coder { -#define EncodeProperty(property, type) [coder encode ## type :self.property forKey:@"property" ] +#define EncodeProperty(property, type) [coder encode ## type: self.property forKey:@#property] EncodeProperty (enableAverageLine, Bool); EncodeProperty (color, Object); EncodeProperty (yValue, Float); diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index b101b20..f948371 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -142,20 +142,56 @@ - (void)drawRect:(CGRect)rect { self.points = [NSMutableArray arrayWithCapacity:self.arrayOfPoints.count]; for (NSUInteger i = 0; i < self.arrayOfPoints.count; i++) { - CGPoint value = CGPointMake(xIndexScale * i, [self.arrayOfPoints[i] CGFloatValue]); - if (value.y < BEMNullGraphValue || !self.interpolateNullValues) { - [self.points addObject:[NSValue valueWithCGPoint:value]]; - } - } + CGFloat value = [self.arrayOfPoints[i] CGFloatValue];; + if (value >= BEMNullGraphValue && self.interpolateNullValues) { + //need to interpolate. For midpoints, just don't add a point + if (i ==0) { + //extrapolate a left edge point from next two actual values + NSUInteger firstPos = 1; //look for first real value + while (firstPos < self.arrayOfPoints.count && [self.arrayOfPoints[firstPos] CGFloatValue] >= BEMNullGraphValue) firstPos++; + if (firstPos >= self.arrayOfPoints.count) break; // all NaNs?? =>don't create any line + + CGFloat firstValue = [self.arrayOfPoints[firstPos] CGFloatValue]; + NSUInteger secondPos = firstPos+1; //look for second real value + while (secondPos < self.arrayOfPoints.count && [self.arrayOfPoints[secondPos] CGFloatValue] >= BEMNullGraphValue) secondPos++; + if (secondPos >= self.arrayOfPoints.count) { + // only one real number + value = firstValue; + } else { + CGFloat delta = firstValue - [self.arrayOfPoints[secondPos] CGFloatValue]; + value = firstValue + firstPos*delta/(secondPos-firstPos); + } - BOOL bezierStatus = self.bezierCurveIsEnabled; - if (self.arrayOfPoints.count <= 2 && self.bezierCurveIsEnabled == YES) bezierStatus = NO; + } else if (i == self.arrayOfPoints.count-1) { + //extrapolate a right edge poit from previous two actual values + NSInteger firstPos = i-1; //look for first real value + while (firstPos >= 0 && [self.arrayOfPoints[firstPos] CGFloatValue] >= BEMNullGraphValue) firstPos--; + if (firstPos < 0 ) continue; // all NaNs?? =>don't create any line; should already be gone + + CGFloat firstValue = [self.arrayOfPoints[firstPos] CGFloatValue]; + NSInteger secondPos = firstPos-1; //look for second real value + while (secondPos >= 0 && [self.arrayOfPoints[secondPos] CGFloatValue] >= BEMNullGraphValue) secondPos--; + if (secondPos < 0) { + // only one real number + value = firstValue; + } else { + CGFloat delta = firstValue - [self.arrayOfPoints[secondPos] CGFloatValue]; + value = firstValue + (self.arrayOfPoints.count - firstPos-1)*delta/(firstPos - secondPos); + } - if (!self.disableMainLine && bezierStatus) { - line = [BEMLine quadCurvedPathWithPoints:self.points]; - fillBottom = [BEMLine quadCurvedPathWithPoints:self.bottomPointsArray]; - fillTop = [BEMLine quadCurvedPathWithPoints:self.topPointsArray]; - } else if (!self.disableMainLine && !bezierStatus) { + } else { + continue; //skip this (middle Null) point, let graphics handle interpolation + } + } + CGPoint newPoint = CGPointMake(xIndexScale * i, value); + [self.points addObject:[NSValue valueWithCGPoint:newPoint]]; + } + + if (!self.disableMainLine && self.bezierCurveIsEnabled) { + line = [BEMLine quadCurvedPathWithPoints:self.points open:YES]; + fillBottom = [BEMLine quadCurvedPathWithPoints:self.bottomPointsArray open:NO]; + fillTop = [BEMLine quadCurvedPathWithPoints:self.topPointsArray open:NO]; + } else if (!self.disableMainLine && !self.bezierCurveIsEnabled) { line = [BEMLine linesToPoints:self.points]; fillBottom = [BEMLine linesToPoints:self.bottomPointsArray]; fillTop = [BEMLine linesToPoints:self.topPointsArray]; @@ -317,34 +353,33 @@ + (UIBezierPath *)linesToPoints:(NSArray *)points { return path; } -+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points { ++ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points open:(BOOL) canSkipPoints { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; [path moveToPoint:p1]; - if (points.count == 2) { - value = points[1]; - CGPoint p2 = [value CGPointValue]; - [path addLineToPoint:p2]; - return path; - } - for (NSValue * point in points) { + if (point == value) continue; //already at first point CGPoint p2 = [point CGPointValue]; - CGPoint midPoint = midPointForPoints(p1, p2); - [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; - [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)]; - + if (canSkipPoints && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { + [path moveToPoint:p2]; + } else { + CGPoint midPoint = midPointForPoints(p1, p2); + [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)]; + [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)]; + } p1 = p2; } return path; } static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) { - return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); + CGFloat avgY = (p1.y + p2.y) / 2.0; + if (isinf(avgY)) avgY = BEMNullGraphValue; + return CGPointMake((p1.x + p2.x) / 2, avgY); } static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) { diff --git a/Classes/BEMSimpleLineGraphView.m b/Classes/BEMSimpleLineGraphView.m index 5ffd149..beca80e 100644 --- a/Classes/BEMSimpleLineGraphView.m +++ b/Classes/BEMSimpleLineGraphView.m @@ -129,6 +129,16 @@ - (instancetype) initWithFrame:(CGRect)frame { - (instancetype) initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) [self commonInit]; + [self restorePropertyWithCoder:coder]; + return self; +} + +-(void) decodeRestorableStateWithCoder:(NSCoder *)coder { + [super decodeRestorableStateWithCoder:coder]; + [self restorePropertyWithCoder:coder]; +} + +-(void) restorePropertyWithCoder:(NSCoder *) coder { #define RestoreProperty(property, type) \ if ([coder containsValueForKey:@#property]) { \ @@ -179,23 +189,26 @@ - (instancetype) initWithCoder:(NSCoder *)coder { RestoreProperty (formatStringForValues, Object); RestoreProperty (averageLine, Object); - return self; #pragma clang diagnostic pop } -- (void) encodeWithEncoder: (NSCoder *)coder { +-(void) encodeRestorableStateWithCoder:(NSCoder *)coder { + [super encodeRestorableStateWithCoder:coder]; + [self encodePropertiesWithCoder:coder]; +} -#define EncodeProperty(property, type) [coder encode ## type: self.property forKey:@#property] +- (void) encodeWithCoder: (NSCoder *)coder { + [super encodeWithCoder:coder]; + [self encodePropertiesWithCoder:coder]; +} +-(void) encodePropertiesWithCoder: (NSCoder *) coder { - [super encodeWithCoder:coder]; +#define EncodeProperty(property, type) [coder encode ## type: self.property forKey:@#property] EncodeProperty (labelFont, Object); EncodeProperty (animationGraphEntranceTime, Float); EncodeProperty (animationGraphStyle, Integer); - EncodeProperty (enableReferenceAxisFrame, Bool); - EncodeProperty (enableTopReferenceAxisFrameLine, Bool); - EncodeProperty (enableRightReferenceAxisFrameLine, Bool); EncodeProperty (colorXaxisLabel, Object); EncodeProperty (colorYaxisLabel, Object); @@ -383,7 +396,7 @@ - (void)layoutNumberOfPoints { } self.noDataLabel.text = noDataText ?: NSLocalizedString(@"No Data", nil); self.noDataLabel.font = self.noDataLabelFont ?: [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; - self.noDataLabel.textColor = self.noDataLabelColor ?: (self.colorLine ?: [UIColor blackColor]); + self.noDataLabel.textColor = self.noDataLabelColor ?: (self.colorXaxisLabel ?: [UIColor blackColor]); [self.viewForFirstBaselineLayout addSubview:self.noDataLabel]; @@ -522,16 +535,20 @@ -(BEMCircle *) circleDotAtIndex:(NSUInteger) index forValue:(CGFloat) dotValue r [yAxisValues addObject:@(positionOnYAxis)]; + BEMCircle *circleDot = nil; + if (reuseNumber < self.circleDots.count) { + circleDot = self.circleDots[reuseNumber]; + } if (dotValue >= BEMNullGraphValue) { // If we're dealing with an null value, don't draw the dot (but put it in yAxis to interpolate line) + [circleDot removeFromSuperview]; return nil; } - BEMCircle *circleDot; CGRect dotFrame = CGRectMake(0, 0, self.sizePoint, self.sizePoint); - if (reuseNumber < self.circleDots.count) { - circleDot = self.circleDots[reuseNumber]; + if (circleDot) { circleDot.frame = dotFrame; + [circleDot setNeedsDisplay]; } else { circleDot = [[BEMCircle alloc] initWithFrame:dotFrame]; [self.circleDots addObject:circleDot]; @@ -573,34 +590,37 @@ - (void)drawDots { BEMCircle * circleDot = [self circleDotAtIndex: index forValue: dotValue reuseNumber: index]; UILabel * label = nil; + if (index < self.permanentPopups.count) { + label = self.permanentPopups[index]; + } else { + label = [[UILabel alloc] initWithFrame:CGRectZero]; + [self.permanentPopups addObject:label ]; + } + if (circleDot) { [self addSubview:circleDot]; - if (self.alwaysDisplayPopUpLabels == YES) { - if (![self.delegate respondsToSelector:@selector(lineGraph:alwaysDisplayPopUpAtIndex:)] || - [self.delegate lineGraph:self alwaysDisplayPopUpAtIndex:index]) { - if (index < self.permanentPopups.count) { - label = self.permanentPopups[index]; - } else { - label = [[UILabel alloc] initWithFrame:CGRectZero]; - [self.permanentPopups addObject:label ]; - } - label = [self configureLabel:label forPoint: circleDot ]; - - [self adjustXLocForLabel:label avoidingDot:circleDot.frame]; - - UILabel * leftNeighbor = (index >= 1 && self.permanentPopups[index-1].superview) ? self.permanentPopups[index-1] : nil; - UILabel * secondNeighbor = (index >= 2 && self.permanentPopups[index-2].superview) ? self.permanentPopups[index-2] : nil; - BOOL showLabel = [self adjustYLocForLabel:label - avoidingDot:circleDot.frame - andNeighbors:leftNeighbor.frame - and:secondNeighbor.frame ]; - if (showLabel) { - [self addSubview:label]; - } else { - [label removeFromSuperview]; - } + if ((self.alwaysDisplayPopUpLabels == YES) && + (![self.delegate respondsToSelector:@selector(lineGraph:alwaysDisplayPopUpAtIndex:)] || + [self.delegate lineGraph:self alwaysDisplayPopUpAtIndex:index])) { + label = [self configureLabel:label forPoint: circleDot ]; + + [self adjustXLocForLabel:label avoidingDot:circleDot.frame]; + + UILabel * leftNeighbor = (index >= 1 && self.permanentPopups[index-1].superview) ? self.permanentPopups[index-1] : nil; + UILabel * secondNeighbor = (index >= 2 && self.permanentPopups[index-2].superview) ? self.permanentPopups[index-2] : nil; + BOOL showLabel = [self adjustYLocForLabel:label + avoidingDot:circleDot.frame + andNeighbors:leftNeighbor.frame + and:secondNeighbor.frame ]; + if (showLabel) { + [self addSubview:label]; + } else { + [label removeFromSuperview]; } + } else { + //not showing labels this time, so remove if any + [label removeFromSuperview]; } // Dot and/or label entrance animation @@ -628,6 +648,8 @@ - (void)drawDots { } completion:nil]; } + } else { + [label removeFromSuperview]; } } for (NSUInteger i = self.circleDots.count -1; i>=numberOfPoints; i--) { @@ -679,6 +701,8 @@ - (void)drawLine { line.verticalReferenceHorizontalFringeNegation = xAxisHorizontalFringeNegationValue; line.arrayOfVerticalReferenceLinePoints = self.enableReferenceXAxisLines ? xAxisLabelPoints : nil; line.arrayOfHorizontalReferenceLinePoints = self.enableReferenceYAxisLines ? yAxisLabelPoints : nil; + } else { + line.enableReferenceLines = NO; } line.color = self.colorLine; @@ -726,8 +750,13 @@ - (void)drawXAxis { } [self addSubview:self.backgroundXAxis]; - self.backgroundXAxis.backgroundColor = self.colorBackgroundXaxis ?: self.colorBottom; - self.backgroundXAxis.alpha = self.alphaBackgroundXaxis; + if (self.colorBackgroundXaxis) { + self.backgroundXAxis.backgroundColor = self.colorBackgroundXaxis; + self.backgroundXAxis.alpha = self.alphaBackgroundXaxis; + } else { + self.backgroundXAxis.backgroundColor = self.colorBottom; + self.backgroundXAxis.alpha = self.alphaBottom; + } NSArray *axisIndices = nil; if ([self.delegate respondsToSelector:@selector(incrementPositionsForXAxisOnLineGraph:)]) { @@ -751,6 +780,7 @@ - (void)drawXAxis { baseIndex = increment - 1 - offset; } } + if (increment == 0) increment = 1; NSMutableArray *values = [NSMutableArray array ]; NSUInteger index = baseIndex; while (index < numberOfPoints) { @@ -933,7 +963,7 @@ - (void)drawYAxis { self.frame.size.width - self.YAxisLabelXOffset - 1.0f: 0.0), 0, - self.YAxisLabelXOffset - 1.0f, + self.YAxisLabelXOffset, self.frame.size.height); if (!self.backgroundYAxis) { @@ -942,8 +972,13 @@ - (void)drawYAxis { self.backgroundYAxis.frame = frameForBackgroundYAxis; } [self addSubview:self.backgroundYAxis]; - self.backgroundYAxis.backgroundColor = self.colorBackgroundYaxis ?: self.colorTop; - self.backgroundYAxis.alpha = self.alphaBackgroundYaxis; + if (self.colorBackgroundYaxis) { + self.backgroundYAxis.backgroundColor = self.colorBackgroundYaxis; + self.backgroundYAxis.alpha = self.alphaBackgroundYaxis; + } else { + self.backgroundYAxis.backgroundColor = self.colorTop; + self.backgroundYAxis.alpha = self.alphaTop; + } [yAxisLabelPoints removeAllObjects]; @@ -1114,14 +1149,13 @@ - (UILabel *)configureLabel: (UILabel *) oldLabel forPoint: (BEMCircle *)circleD NSNumber *value = (index <= dataPoints.count) ? value = dataPoints[index] : @(0); // @((NSInteger) circleDot.absoluteValue) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-nonliteral" + //note this can indeed crash if delegate provides junk for formatString (e.g. %@); try/catch doesn't work NSString *formattedValue = [NSString stringWithFormat:self.formatStringForValues, value.doubleValue]; #pragma clang diagnostic pop newPopUpLabel.text = [NSString stringWithFormat:@"%@%@%@", prefix, formattedValue, suffix]; } - NSLog(@"%@ before SizeToFit: %@",newPopUpLabel.text, NSStringFromCGRect(newPopUpLabel.frame)); CGSize requiredSize = [newPopUpLabel sizeThatFits:CGSizeMake(100.0f, CGFLOAT_MAX)]; newPopUpLabel.frame = CGRectMake(10, 10, requiredSize.width+10.0f, requiredSize.height+10.0f); - NSLog(@"%@ after SizeToFit: %@",newPopUpLabel.text, NSStringFromCGRect(newPopUpLabel.frame)); return newPopUpLabel; } From a10ad12f7bb45b4440d4b9767575d78f78e97e8a Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sun, 26 Mar 2017 12:17:15 -0700 Subject: [PATCH 06/15] Allow use on iPhone and Split View --- .../SimpleLineChart.xcodeproj/project.pbxproj | 8 +- Sample Project/TestBed/AppIcon60x60@2x.png | Bin 17651 -> 0 bytes .../AppIcon.appiconset/Contents.json | 78 ++++++++ .../TestBed/Assets.xcassets/Contents.json | 6 + .../TestBed/Base.lproj/Main.storyboard | 172 ++++++++++-------- Sample Project/TestBed/DetailViewController.m | 4 +- Sample Project/TestBed/Info.plist | 6 + Sample Project/TestBed/MasterViewController.m | 64 ++++--- Sample Project/TestBed/StatsViewController.m | 16 +- 9 files changed, 234 insertions(+), 120 deletions(-) delete mode 100644 Sample Project/TestBed/AppIcon60x60@2x.png create mode 100644 Sample Project/TestBed/Assets.xcassets/Contents.json diff --git a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj index bd6f6d1..10dc0ee 100644 --- a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj +++ b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 1E960AA61E7E097C000E2BB8 /* BEMLine.m in Sources */ = {isa = PBXBuildFile; fileRef = C3B90A5B187D15F7003E407D /* BEMLine.m */; }; 1E960AA71E7E097C000E2BB8 /* BEMAverageLine.m in Sources */ = {isa = PBXBuildFile; fileRef = A63990B41AD4923900B14D88 /* BEMAverageLine.m */; }; 1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */ = {isa = PBXBuildFile; fileRef = A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */; }; - 1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */; }; 1E960B121E7F9C80000E2BB8 /* MSColorComponentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */; }; 1E960B131E7F9C80000E2BB8 /* MSColorSelectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B001E7F9C80000E2BB8 /* MSColorSelectionView.m */; }; 1E960B141E7F9C80000E2BB8 /* MSColorSelectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960B021E7F9C80000E2BB8 /* MSColorSelectionViewController.m */; }; @@ -85,7 +84,6 @@ 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; 1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; - 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "AppIcon60x60@2x.png"; sourceTree = ""; }; 1E960AFC1E7F9C80000E2BB8 /* MSColorComponentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorComponentView.h; sourceTree = ""; }; 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorComponentView.m; sourceTree = ""; }; 1E960AFE1E7F9C80000E2BB8 /* MSColorPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorPicker.h; sourceTree = ""; }; @@ -203,7 +201,6 @@ children = ( 1E960A941E7DF942000E2BB8 /* Info.plist */, 1E960A911E7DF942000E2BB8 /* LaunchScreen.storyboard */, - 1E960AA91E7EDFE4000E2BB8 /* AppIcon60x60@2x.png */, 1E960A841E7DF942000E2BB8 /* main.m */, ); name = "Supporting Files"; @@ -443,7 +440,6 @@ files = ( 1E960A931E7DF942000E2BB8 /* LaunchScreen.storyboard in Resources */, 1E960A901E7DF942000E2BB8 /* Assets.xcassets in Resources */, - 1E960AAA1E7EDFE4000E2BB8 /* AppIcon60x60@2x.png in Resources */, 1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -602,7 +598,7 @@ MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -622,7 +618,7 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = BorisEmorine.TestBed; PRODUCT_NAME = "$(TARGET_NAME)"; - TARGETED_DEVICE_FAMILY = 2; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; diff --git a/Sample Project/TestBed/AppIcon60x60@2x.png b/Sample Project/TestBed/AppIcon60x60@2x.png deleted file mode 100644 index 5c7a53230ace7ae5f1d1c694578877b043ed4efb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17651 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 avUuj#mhCI8C%69$iGr-E%ma*R;Qs>q8E+K; diff --git a/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/Contents.json b/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/Contents.json index e59ae2c..d548d25 100644 --- a/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Sample Project/TestBed/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,63 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "AppIcon29x29@2x.png", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "AppIcon40x40@2x.png", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "57x57", + "scale" : "1x" + }, + { + "idiom" : "iphone", + "size" : "57x57", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "AppIcon60x60@2x.png", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, { "idiom" : "ipad", "size" : "20x20", @@ -30,6 +88,26 @@ "size" : "40x40", "scale" : "2x" }, + { + "idiom" : "ipad", + "size" : "50x50", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "50x50", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "72x72", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "72x72", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "76x76", diff --git a/Sample Project/TestBed/Assets.xcassets/Contents.json b/Sample Project/TestBed/Assets.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Sample Project/TestBed/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Sample Project/TestBed/Base.lproj/Main.storyboard b/Sample Project/TestBed/Base.lproj/Main.storyboard index be6492c..551eec8 100644 --- a/Sample Project/TestBed/Base.lproj/Main.storyboard +++ b/Sample Project/TestBed/Base.lproj/Main.storyboard @@ -121,14 +121,6 @@ - - - - - - - - @@ -144,6 +136,14 @@ + + + + + + + + @@ -213,7 +213,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -287,7 +287,7 @@ - + @@ -317,7 +317,7 @@ - + @@ -351,7 +351,7 @@ - + @@ -384,7 +384,7 @@ - + @@ -421,7 +421,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -495,7 +495,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -565,7 +565,7 @@ - + @@ -598,7 +598,7 @@ - + @@ -635,7 +635,7 @@ - + @@ -672,7 +672,7 @@ - + @@ -709,7 +709,7 @@ - + @@ -746,7 +746,7 @@ - + @@ -783,7 +783,7 @@ - + @@ -820,7 +820,7 @@ - + @@ -861,7 +861,7 @@ - + @@ -891,7 +891,7 @@ - + @@ -928,7 +928,7 @@ - + @@ -969,7 +969,7 @@ - + @@ -1006,7 +1006,7 @@ - + @@ -1040,7 +1040,7 @@ - + @@ -1074,7 +1074,7 @@ - + @@ -1104,7 +1104,7 @@ - + @@ -1206,7 +1206,7 @@ - + @@ -1239,7 +1239,7 @@ - + @@ -1272,7 +1272,7 @@ - + @@ -1309,7 +1309,7 @@ - + @@ -1342,7 +1342,7 @@ - + @@ -1375,7 +1375,7 @@ - + @@ -1408,7 +1408,7 @@ - + @@ -1445,7 +1445,7 @@ - + @@ -1482,7 +1482,7 @@ - + @@ -1519,7 +1519,7 @@ - + @@ -1552,7 +1552,7 @@ - + @@ -1585,7 +1585,7 @@ - + @@ -1626,7 +1626,7 @@ - + @@ -1657,7 +1657,7 @@ - + @@ -1694,7 +1694,7 @@ - + @@ -1727,7 +1727,7 @@ - + @@ -1760,7 +1760,7 @@ - + @@ -1801,7 +1801,7 @@ - + @@ -1835,7 +1835,7 @@ - + @@ -1872,7 +1872,7 @@ - + @@ -1913,7 +1913,7 @@ - + @@ -1950,7 +1950,7 @@ - + @@ -1983,7 +1983,7 @@ - + @@ -2020,7 +2020,7 @@ - + @@ -2057,7 +2057,7 @@ - + @@ -2090,7 +2090,7 @@ - + @@ -2127,7 +2127,7 @@ - + @@ -2164,7 +2164,7 @@ - + @@ -2197,7 +2197,7 @@ - + @@ -2230,7 +2230,7 @@ - + @@ -2267,7 +2267,7 @@ - + @@ -2304,7 +2304,7 @@ - + @@ -2341,7 +2341,7 @@ - + @@ -2378,7 +2378,7 @@ - + @@ -2415,7 +2415,7 @@ - + @@ -2452,7 +2452,7 @@ - + @@ -2489,7 +2489,7 @@ - + @@ -2526,7 +2526,7 @@ - + @@ -2563,7 +2563,7 @@ - + @@ -2600,7 +2600,7 @@ - + @@ -2644,7 +2644,7 @@ - + @@ -2716,6 +2716,7 @@ + @@ -3025,7 +3026,7 @@ - + @@ -3066,11 +3067,30 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Sample Project/TestBed/DetailViewController.m b/Sample Project/TestBed/DetailViewController.m index d358826..6987f20 100644 --- a/Sample Project/TestBed/DetailViewController.m +++ b/Sample Project/TestBed/DetailViewController.m @@ -31,6 +31,8 @@ @implementation DetailViewController - (void)viewDidLoad { [super viewDidLoad]; + self.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + self.navigationItem.leftItemsSupplementBackButton = YES; self.maxValue = -1.0; self.minValue = -1.0; @@ -150,7 +152,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([segue.identifier isEqualToString:@"showStats"]) { BEMGraphCalculator * calc = [BEMGraphCalculator sharedCalculator]; - StatsViewController *controller = segue.destinationViewController; + StatsViewController *controller = (StatsViewController *)((UINavigationController *)segue.destinationViewController).topViewController; controller.standardDeviation = [self formatNumber:[calc calculateStandardDeviationOnGraph:self.myGraph]]; controller.average = [self formatNumber:[calc calculatePointValueAverageOnGraph:self.myGraph]]; controller.median = [self formatNumber:[calc calculatePointValueMedianOnGraph: self.myGraph]]; diff --git a/Sample Project/TestBed/Info.plist b/Sample Project/TestBed/Info.plist index f4b34c9..d052473 100644 --- a/Sample Project/TestBed/Info.plist +++ b/Sample Project/TestBed/Info.plist @@ -28,6 +28,12 @@ armv7 + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait diff --git a/Sample Project/TestBed/MasterViewController.m b/Sample Project/TestBed/MasterViewController.m index 858f449..789856a 100644 --- a/Sample Project/TestBed/MasterViewController.m +++ b/Sample Project/TestBed/MasterViewController.m @@ -89,7 +89,9 @@ -(BOOL) on { @interface MasterViewController () -@property (weak, nonatomic) IBOutlet BEMSimpleLineGraphView *myGraph; +@property (nonatomic) BOOL hasRestoredUI; + +@property (strong, nonatomic) IBOutlet BEMSimpleLineGraphView *myGraph; @property (strong, nonatomic) NSDictionary *methodList; @@ -197,19 +199,25 @@ CGGradientRef createGradient () { - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. - self.navigationItem.leftBarButtonItem = self.editButtonItem; - + self.hasRestoredUI = NO; self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; } -(void) viewWillAppear:(BOOL)animated { - [super viewDidAppear:animated]; - [self.detailViewController loadViewIfNeeded]; - self.myGraph = self.detailViewController.myGraph; - [self restoreUI]; + [super viewWillAppear:animated]; + if (!self.hasRestoredUI) [self restoreUI]; + NSLog(@"VWA"); +} + +-(void) decodeRestorableStateWithCoder:(NSCoder *)coder { + [super decodeRestorableStateWithCoder:coder]; + [self restoreUI]; //kludge for VWA not getting called during restore } -(void) restoreUI { + [self.detailViewController loadViewIfNeeded]; + self.myGraph = self.detailViewController.myGraph; + self.hasRestoredUI = YES; self.widthLine.floatValue = self.myGraph.widthLine; self.staticPaddingField.floatValue = self.detailViewController.staticPaddingValue; @@ -804,9 +812,10 @@ - (IBAction)enableGradientHoriz:(UISwitch *)sender { - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"showDetail"]) { - DetailViewController *controller = (DetailViewController *)[[segue destinationViewController] topViewController]; - controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; - controller.navigationItem.leftItemsSupplementBackButton = YES; + UINavigationController* navigationController = (UINavigationController*)[segue destinationViewController]; + navigationController.viewControllers = @[self.detailViewController]; + self.detailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; + self.detailViewController.navigationItem.leftItemsSupplementBackButton = YES; } else if ([[segue identifier] isEqualToString:@"FontPicker"]) { ARFontPickerViewController * controller = (ARFontPickerViewController*) [segue destinationViewController]; controller.delegate = self; @@ -818,7 +827,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { // CGRect cellFrame = [self.view convertRect:((UIView *)sender).bounds fromView:sender]; destNav.popoverPresentationController.sourceView = ((UIView *)sender) ; destNav.popoverPresentationController.sourceRect = ((UIView *)sender).bounds ; - destNav.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionLeft; + destNav.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny; destNav.preferredContentSize = [[destNav visibleViewController].view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; MSColorSelectionViewController *colorSelectionController = (MSColorSelectionViewController *)destNav.visibleViewController; colorSelectionController.delegate = self; @@ -842,28 +851,27 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { self.currentColorKey = segue.identifier; NSAssert(self.currentColorKey != nil && self.currentColorChip != nil, @"View Structural problem"); - //add numbers to color selector - //temp turn off animation in graph - //remember original color - //set color to last color set if any to copy from one to another - self.saveColorSetting = (UIColor *) [self.myGraph valueForKey:self.currentColorKey]; - if (!self.saveColorSetting) { - //value is not currently set + + UIColor * oldColor = (UIColor *) [self.myGraph valueForKey:self.currentColorKey]; + if (!oldColor) { + //value is not currently set; handle special cases that default to others if ([self.currentColorKey isEqualToString:@"colorBackgroundYaxis"]) { - self.myGraph.colorBackgroundYaxis = self.myGraph.colorTop; - self.currentColorChip.backgroundColor = self.myGraph.colorTop; + oldColor = self.myGraph.colorTop; + self.myGraph.colorBackgroundYaxis = oldColor; } else if ([self.currentColorKey isEqualToString:@"colorBackgroundXaxis"]) { - self.myGraph.colorBackgroundXaxis = self.myGraph.colorBottom; - self.currentColorChip.backgroundColor = self.myGraph.colorBottom; + oldColor = self.myGraph.colorBottom; + self.myGraph.colorBackgroundXaxis = oldColor; } else { - self.saveColorSetting = [UIColor blueColor]; - [self didChangeColor:self.saveColorSetting]; + oldColor = [UIColor blueColor]; //shouldn't happen + [self didChangeColor:oldColor]; } + self.currentColorChip.backgroundColor = oldColor; } + self.saveColorSetting = oldColor; self.saveAnimationSetting = self.myGraph.animationGraphStyle; self.myGraph.animationGraphStyle = BEMLineAnimationNone; - colorSelectionController.color = (UIColor * _Nonnull)(self.saveColorSetting ) ?: self.currentColorChip.backgroundColor; + colorSelectionController.color = oldColor; } @@ -889,8 +897,12 @@ - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *) // Return NO if you do not want the specified item to be editable. return NO; } + -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; + if (self.splitViewController.isCollapsed) { + [self performSegueWithIdentifier:@"showDetail" sender:self]; + } } #pragma mark TextDelegate @@ -900,7 +912,5 @@ -(BOOL) textFieldShouldReturn:(UITextField *)textField { return YES; } - - @end diff --git a/Sample Project/TestBed/StatsViewController.m b/Sample Project/TestBed/StatsViewController.m index b387c6d..9c1f934 100644 --- a/Sample Project/TestBed/StatsViewController.m +++ b/Sample Project/TestBed/StatsViewController.m @@ -18,6 +18,11 @@ @implementation StatsViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(exit:)]; +} + +-(void) exit: (id) sender { + [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; } - (void)viewWillAppear:(BOOL)animated { @@ -25,21 +30,12 @@ - (void)viewWillAppear:(BOOL)animated { self.standardDeviationLabel.text = self.standardDeviation; self.averageLabel.text = self.average; self.medianLabel.text = self.median; - self. modeLabel.text = self.mode; + self.modeLabel.text = self.mode; self.maximumLabel.text = self.maximum; self.minimumLabel.text = self.minimum; self.snapshotImageView.image = self.snapshotImage; } -- (void)didReceiveMemoryWarning { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return (section == 0) ? 6 : 1; -} - - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; From 1169854f8451833288ee865350e98a71f6de7d6b Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sun, 26 Mar 2017 12:17:45 -0700 Subject: [PATCH 07/15] Fix bug with TouchLineInput color (doesn't change after initial setting) --- Classes/BEMSimpleLineGraphView.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/BEMSimpleLineGraphView.m b/Classes/BEMSimpleLineGraphView.m index beca80e..2aa16d2 100644 --- a/Classes/BEMSimpleLineGraphView.m +++ b/Classes/BEMSimpleLineGraphView.m @@ -430,9 +430,9 @@ - (void)layoutTouchReport { // Initialize the vertical gray line that appears where the user touches the graph. if (!self.touchInputLine) { self.touchInputLine = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.widthTouchInputLine, self.frame.size.height)]; - self.touchInputLine.backgroundColor = self.colorTouchInputLine; - self.touchInputLine.alpha = 0; } + self.touchInputLine.alpha = 0; + self.touchInputLine.backgroundColor = self.colorTouchInputLine; [self addSubview:self.touchInputLine]; if (!self.panView) { From 019aa1e1517de60d55f63a4843728bfa63308964 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Mon, 27 Mar 2017 20:07:00 -0700 Subject: [PATCH 08/15] Bug fixes: Line clipsToBounds to avoid drawing outside the box (when extrapolating nulls or user set minValue too High) Top/Bottom reference lines were outside box When Bezier off and Interpolate nulls off, properly draws line segments now Removed VMA NSLog from TestBed/MasterController --- Classes/BEMLine.m | 30 ++++++++++++------- Classes/BEMSimpleLineGraphView.m | 9 ++++-- Sample Project/TestBed/MasterViewController.m | 1 - 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index f948371..a21c3f8 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -33,6 +33,7 @@ - (instancetype)initWithFrame:(CGRect)frame { _enableLeftReferenceFrameLine = YES; _enableBottomReferenceFrameLine = YES; _interpolateNullValues = YES; + self.clipsToBounds = YES; } return self; } @@ -59,8 +60,8 @@ - (void)drawRect:(CGRect)rect { if (self.enableReferenceFrame == YES) { if (self.enableBottomReferenceFrameLine) { // Bottom Line - [referenceFramePath moveToPoint:CGPointMake(0, self.frame.size.height)]; - [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height)]; + [referenceFramePath moveToPoint:CGPointMake(0, self.frame.size.height-self.referenceLineWidth/4)]; + [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height-self.referenceLineWidth/4)]; } if (self.enableLeftReferenceFrameLine) { @@ -71,8 +72,8 @@ - (void)drawRect:(CGRect)rect { if (self.enableTopReferenceFrameLine) { // Top Line - [referenceFramePath moveToPoint:CGPointMake(0+self.referenceLineWidth/4, 0)]; - [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, 0)]; + [referenceFramePath moveToPoint:CGPointMake(0+self.referenceLineWidth/4, self.referenceLineWidth/4)]; + [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.referenceLineWidth/4)]; } if (self.enableRightReferenceFrameLine) { @@ -192,12 +193,12 @@ - (void)drawRect:(CGRect)rect { fillBottom = [BEMLine quadCurvedPathWithPoints:self.bottomPointsArray open:NO]; fillTop = [BEMLine quadCurvedPathWithPoints:self.topPointsArray open:NO]; } else if (!self.disableMainLine && !self.bezierCurveIsEnabled) { - line = [BEMLine linesToPoints:self.points]; - fillBottom = [BEMLine linesToPoints:self.bottomPointsArray]; - fillTop = [BEMLine linesToPoints:self.topPointsArray]; + line = [BEMLine linesToPoints:self.points open:YES]; + fillBottom = [BEMLine linesToPoints:self.bottomPointsArray open:NO]; + fillTop = [BEMLine linesToPoints:self.topPointsArray open:NO]; } else { - fillBottom = [BEMLine linesToPoints:self.bottomPointsArray]; - fillTop = [BEMLine linesToPoints:self.topPointsArray]; + fillBottom = [BEMLine linesToPoints:self.bottomPointsArray open:NO]; + fillTop = [BEMLine linesToPoints:self.topPointsArray open:NO]; } //----------------------------// @@ -340,15 +341,22 @@ - (void)drawRect:(CGRect)rect { return bottomPoints; } -+ (UIBezierPath *)linesToPoints:(NSArray *)points { ++ (UIBezierPath *)linesToPoints:(NSArray *)points open:(BOOL) canSkipPoints { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; [path moveToPoint:p1]; for (NSValue * point in points) { + if (point == value) continue; //already at first point CGPoint p2 = [point CGPointValue]; - [path addLineToPoint:p2]; + + if (canSkipPoints && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { + [path moveToPoint:p2]; + } else { + [path addLineToPoint:p2]; + } + p1 = p2; } return path; } diff --git a/Classes/BEMSimpleLineGraphView.m b/Classes/BEMSimpleLineGraphView.m index 2aa16d2..0577405 100644 --- a/Classes/BEMSimpleLineGraphView.m +++ b/Classes/BEMSimpleLineGraphView.m @@ -519,7 +519,11 @@ - (CGFloat) calculateWidestLabel { } else { widestNumber = [self labelWidthForValue:self.frame.size.height] ; } - return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); + if (self.averageLine.enableAverageLine) { + return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); + } else { + return widestNumber; + } } @@ -1003,6 +1007,7 @@ - (void)drawYAxis { if ([self.delegate respondsToSelector:@selector(baseValueForYAxisOnLineGraph:)] && [self.delegate respondsToSelector:@selector(incrementValueForYAxisOnLineGraph:)]) { value = [self.delegate baseValueForYAxisOnLineGraph:self]; increment = [self.delegate incrementValueForYAxisOnLineGraph:self]; + if (increment <= 0) increment = 1; numberOfLabels = (NSUInteger) ((self.maxValue - value)/increment)+1; if (numberOfLabels > 100) { NSLog(@"[BEMSimpleLineGraph] Increment does not properly lay out Y axis, bailing early"); @@ -1447,7 +1452,7 @@ - (CGFloat)yPositionForDotValue:(CGFloat)dotValue { CGFloat percentValue = (dotValue - self.minValue) / (self.maxValue - self.minValue); CGFloat topOfChart = self.frame.size.height - padding/2.0f; CGFloat sizeOfChart = self.frame.size.height - padding; - positionOnYAxis = topOfChart - percentValue * sizeOfChart + self.XAxisLabelYOffset/2; + positionOnYAxis = topOfChart - percentValue * sizeOfChart + self.XAxisLabelYOffset; } } else { positionOnYAxis = ((self.frame.size.height) - dotValue); diff --git a/Sample Project/TestBed/MasterViewController.m b/Sample Project/TestBed/MasterViewController.m index 789856a..d63e191 100644 --- a/Sample Project/TestBed/MasterViewController.m +++ b/Sample Project/TestBed/MasterViewController.m @@ -206,7 +206,6 @@ - (void)viewDidLoad { -(void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (!self.hasRestoredUI) [self restoreUI]; - NSLog(@"VWA"); } -(void) decodeRestorableStateWithCoder:(NSCoder *)coder { From 0e33b9892c86f30ebfd2d251b9fd25e13d2d01dc Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sun, 14 May 2017 08:48:19 -0700 Subject: [PATCH 09/15] Requested changes to PR 3295 Fixing breaks, spaces to match project style Explaining constant 4 in BEMLine (note bug on line76 0+offset should be 0) --- Classes/BEMAverageLine.m | 4 +- Classes/BEMLine.m | 31 ++++++------ Classes/BEMSimpleLineGraphView.h | 4 +- Classes/BEMSimpleLineGraphView.m | 27 +++++------ .../SimpleLineChart.xcodeproj/project.pbxproj | 10 ++-- .../SimpleLineChart/StatsViewController.m | 1 - Sample Project/TestBed/AppDelegate.m | 29 ----------- Sample Project/TestBed/DetailViewController.m | 8 ++-- Sample Project/TestBed/MasterViewController.m | 48 +++++++++---------- Sample Project/TestBed/StatsViewController.m | 2 +- 10 files changed, 67 insertions(+), 97 deletions(-) diff --git a/Classes/BEMAverageLine.m b/Classes/BEMAverageLine.m index 3aa8c31..585b19e 100644 --- a/Classes/BEMAverageLine.m +++ b/Classes/BEMAverageLine.m @@ -60,14 +60,14 @@ - (void) encodeWithCoder: (NSCoder *)coder { --(void) setLabel:(UILabel *)label { +- (void)setLabel:(UILabel *)label { if (_label != label) { [_label removeFromSuperview]; _label = label; } } --(void) dealloc { +- (void)dealloc { self.label= nil; } @end diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index a21c3f8..6abf8c5 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -58,35 +58,36 @@ - (void)drawRect:(CGRect)rect { referenceFramePath.lineWidth = 0.7f; if (self.enableReferenceFrame == YES) { + CGFloat offset = self.referenceLineWidth/4; //moves framing ref line slightly into view if (self.enableBottomReferenceFrameLine) { // Bottom Line - [referenceFramePath moveToPoint:CGPointMake(0, self.frame.size.height-self.referenceLineWidth/4)]; - [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height-self.referenceLineWidth/4)]; + [referenceFramePath moveToPoint: CGPointMake(0, self.frame.size.height-offset)]; + [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.frame.size.height-offset)]; } if (self.enableLeftReferenceFrameLine) { // Left Line - [referenceFramePath moveToPoint:CGPointMake(0+self.referenceLineWidth/4, self.frame.size.height)]; - [referenceFramePath addLineToPoint:CGPointMake(0+self.referenceLineWidth/4, 0)]; + [referenceFramePath moveToPoint: CGPointMake(0+offset, self.frame.size.height)]; + [referenceFramePath addLineToPoint:CGPointMake(0+offset, 0)]; } if (self.enableTopReferenceFrameLine) { // Top Line - [referenceFramePath moveToPoint:CGPointMake(0+self.referenceLineWidth/4, self.referenceLineWidth/4)]; - [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, self.referenceLineWidth/4)]; + [referenceFramePath moveToPoint: CGPointMake(0+offset, offset)]; + [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, offset)]; } if (self.enableRightReferenceFrameLine) { // Right Line - [referenceFramePath moveToPoint:CGPointMake(self.frame.size.width - self.referenceLineWidth/4, self.frame.size.height)]; - [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width - self.referenceLineWidth/4, 0)]; + [referenceFramePath moveToPoint: CGPointMake(self.frame.size.width - offset, self.frame.size.height)]; + [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width - offset, 0)]; } } if (self.enableReferenceLines == YES) { if (self.arrayOfVerticalReferenceLinePoints.count > 0) { for (NSNumber *xNumber in self.arrayOfVerticalReferenceLinePoints) { - CGFloat xValue =[xNumber doubleValue]; + CGFloat xValue = [xNumber doubleValue]; if (self.verticalReferenceHorizontalFringeNegation != 0.0) { NSUInteger index = [self.arrayOfVerticalReferenceLinePoints indexOfObject:xNumber]; if (index == 0) { // far left reference line @@ -143,10 +144,10 @@ - (void)drawRect:(CGRect)rect { self.points = [NSMutableArray arrayWithCapacity:self.arrayOfPoints.count]; for (NSUInteger i = 0; i < self.arrayOfPoints.count; i++) { - CGFloat value = [self.arrayOfPoints[i] CGFloatValue];; + CGFloat value = [self.arrayOfPoints[i] CGFloatValue]; if (value >= BEMNullGraphValue && self.interpolateNullValues) { //need to interpolate. For midpoints, just don't add a point - if (i ==0) { + if (i == 0) { //extrapolate a left edge point from next two actual values NSUInteger firstPos = 1; //look for first real value while (firstPos < self.arrayOfPoints.count && [self.arrayOfPoints[firstPos] CGFloatValue] >= BEMNullGraphValue) firstPos++; @@ -341,7 +342,7 @@ - (void)drawRect:(CGRect)rect { return bottomPoints; } -+ (UIBezierPath *)linesToPoints:(NSArray *)points open:(BOOL) canSkipPoints { ++ (UIBezierPath *)linesToPoints:(NSArray *)points open:(BOOL)open { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; CGPoint p1 = [value CGPointValue]; @@ -351,7 +352,7 @@ + (UIBezierPath *)linesToPoints:(NSArray *)points open:(BOOL) canSki if (point == value) continue; //already at first point CGPoint p2 = [point CGPointValue]; - if (canSkipPoints && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { + if (open && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { [path moveToPoint:p2]; } else { [path addLineToPoint:p2]; @@ -361,7 +362,7 @@ + (UIBezierPath *)linesToPoints:(NSArray *)points open:(BOOL) canSki return path; } -+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points open:(BOOL) canSkipPoints { ++ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points open:(BOOL)open { UIBezierPath *path = [UIBezierPath bezierPath]; NSValue *value = points[0]; @@ -372,7 +373,7 @@ + (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points open:(B if (point == value) continue; //already at first point CGPoint p2 = [point CGPointValue]; - if (canSkipPoints && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { + if (open && (p1.y >= BEMNullGraphValue || p2.y >= BEMNullGraphValue)) { [path moveToPoint:p2]; } else { CGPoint midPoint = midPointForPoints(p1, p2); diff --git a/Classes/BEMSimpleLineGraphView.h b/Classes/BEMSimpleLineGraphView.h index 12917f7..9d38dcd 100644 --- a/Classes/BEMSimpleLineGraphView.h +++ b/Classes/BEMSimpleLineGraphView.h @@ -487,11 +487,13 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView *circleDots; /// The line itself -@property (strong, nonatomic) BEMLine * masterLine; +@property (strong, nonatomic) BEMLine *masterLine; /// The vertical line which appears when the user drags across the graph @property (strong, nonatomic) UIView *touchInputLine; @@ -133,12 +133,12 @@ - (instancetype) initWithCoder:(NSCoder *)coder { return self; } --(void) decodeRestorableStateWithCoder:(NSCoder *)coder { +- (void)decodeRestorableStateWithCoder:(NSCoder *)coder { [super decodeRestorableStateWithCoder:coder]; [self restorePropertyWithCoder:coder]; } --(void) restorePropertyWithCoder:(NSCoder *) coder { +- (void)restorePropertyWithCoder:(NSCoder *)coder { #define RestoreProperty(property, type) \ if ([coder containsValueForKey:@#property]) { \ @@ -192,7 +192,7 @@ -(void) restorePropertyWithCoder:(NSCoder *) coder { #pragma clang diagnostic pop } --(void) encodeRestorableStateWithCoder:(NSCoder *)coder { +- (void)encodeRestorableStateWithCoder:(NSCoder *)coder { [super encodeRestorableStateWithCoder:coder]; [self encodePropertiesWithCoder:coder]; } @@ -202,7 +202,7 @@ - (void) encodeWithCoder: (NSCoder *)coder { [self encodePropertiesWithCoder:coder]; } --(void) encodePropertiesWithCoder: (NSCoder *) coder { +- (void)encodePropertiesWithCoder:(NSCoder *)coder { #define EncodeProperty(property, type) [coder encode ## type: self.property forKey:@#property] @@ -357,7 +357,7 @@ - (void)layoutSubviews { [self drawGraph]; } --(void) clearGraph { +- (void)clearGraph { for (UIView * subvView in self.subviews) { [subvView removeFromSuperview]; } @@ -503,7 +503,7 @@ - (void)drawEntireGraph { [self drawYAxis]; } --(CGFloat) labelWidthForValue:(CGFloat) value { +- (CGFloat)labelWidthForValue:(CGFloat)value { NSDictionary *attributes = @{NSFontAttributeName: self.labelFont}; NSString *valueString = [self yAxisTextForValue:value]; NSString *labelString = [valueString stringByReplacingOccurrencesOfString:@"[0-9-]" withString:@"N" options:NSRegularExpressionSearch range:NSMakeRange(0, [valueString length])]; @@ -520,14 +520,14 @@ - (CGFloat) calculateWidestLabel { widestNumber = [self labelWidthForValue:self.frame.size.height] ; } if (self.averageLine.enableAverageLine) { - return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); + return MAX(widestNumber, [self.averageLine.title sizeWithAttributes:attributes].width); } else { return widestNumber; } } --(BEMCircle *) circleDotAtIndex:(NSUInteger) index forValue:(CGFloat) dotValue reuseNumber: (NSUInteger) reuseNumber { +- (BEMCircle *)circleDotAtIndex:(NSUInteger)index forValue:(CGFloat)dotValue reuseNumber:(NSUInteger)reuseNumber { CGFloat positionOnXAxis = numberOfPoints > 1 ? (((self.frame.size.width - self.YAxisLabelXOffset) / (numberOfPoints - 1)) * index) : self.frame.size.width/2; @@ -895,7 +895,7 @@ - (UILabel *)xAxisLabelWithText:(NSString *)text atIndex:(NSUInteger)index reuse return labelXAxis; } --(NSString *) yAxisTextForValue:(CGFloat) value { +- (NSString *)yAxisTextForValue:(CGFloat) value { NSString *yAxisSuffix = @""; NSString *yAxisPrefix = @""; @@ -1164,7 +1164,7 @@ - (UILabel *)configureLabel: (UILabel *) oldLabel forPoint: (BEMCircle *)circleD return newPopUpLabel; } --(void) adjustXLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) circleDotFrame { +- (void)adjustXLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) circleDotFrame { //now fixup left/right layout issues CGFloat xCenter = CGRectGetMidX(circleDotFrame); @@ -1185,7 +1185,7 @@ -(void) adjustXLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) circleDo popUpLabel.center = CGPointMake(xCenter, popUpLabel.center.y); } --(BOOL) adjustYLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) dotFrame andNeighbors: (CGRect) leftNeightbor and: (CGRect) secondNeighbor { +- (BOOL)adjustYLocForLabel:(UIView *)popUpLabel avoidingDot:(CGRect)dotFrame andNeighbors:(CGRect)leftNeightbor and:(CGRect)secondNeighbor { //returns YES if it can avoid those neighbors //note: nil.frame == CGRectZero //check for bumping into top OR overlap with left neighbors @@ -1209,7 +1209,6 @@ -(BOOL) adjustYLocForLabel: (UIView *) popUpLabel avoidingDot: (CGRect) dotFrame return YES; } - - (UIImage *)graphSnapshotImage { return [self graphSnapshotImageRenderedWhileInBackground:NO]; } @@ -1233,7 +1232,6 @@ - (UIImage *)graphSnapshotImageRenderedWhileInBackground:(BOOL)appIsInBackground - (void)reloadGraph { [self drawGraph]; - // [self setNeedsLayout]; } #pragma mark - Values @@ -1464,7 +1462,6 @@ - (CGFloat)yPositionForDotValue:(CGFloat)dotValue { #pragma mark - Deprecated Methods - - (NSNumber *)calculatePointValueSum { [self printDeprecationTransitionWarningForOldMethod:@"calculatePointValueSum" replacementMethod:@"calculatePointValueSumOnGraph:" newObject:@"BEMGraphCalculator" sharedInstance:YES]; return [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:self]; diff --git a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj index 10dc0ee..c893261 100644 --- a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj +++ b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj @@ -79,11 +79,11 @@ 1E960A981E7DF9BB000E2BB8 /* ARFontPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARFontPickerViewController.h; sourceTree = ""; }; 1E960A991E7DF9BB000E2BB8 /* ARFontPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARFontPickerViewController.m; sourceTree = ""; }; 1E960A9A1E7DF9BB000E2BB8 /* DetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DetailViewController.h; sourceTree = ""; }; - 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DetailViewController.m; sourceTree = ""; }; + 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = DetailViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MasterViewController.h; sourceTree = ""; }; - 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MasterViewController.m; sourceTree = ""; }; + 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MasterViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatsViewController.h; sourceTree = ""; }; - 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatsViewController.m; sourceTree = ""; }; + 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = StatsViewController.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1E960AFC1E7F9C80000E2BB8 /* MSColorComponentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorComponentView.h; sourceTree = ""; }; 1E960AFD1E7F9C80000E2BB8 /* MSColorComponentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSColorComponentView.m; sourceTree = ""; }; 1E960AFE1E7F9C80000E2BB8 /* MSColorPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSColorPicker.h; sourceTree = ""; }; @@ -111,7 +111,7 @@ 99B3FA381877898B00539A7B /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 99B3FA391877898B00539A7B /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = README.md; path = ../README.md; sourceTree = ""; }; A63990B31AD4923900B14D88 /* BEMAverageLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMAverageLine.h; sourceTree = ""; }; - A63990B41AD4923900B14D88 /* BEMAverageLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMAverageLine.m; sourceTree = ""; }; + A63990B41AD4923900B14D88 /* BEMAverageLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = BEMAverageLine.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; A64594511BAB257B00D6B8FD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "Base.lproj/Launch Screen.storyboard"; sourceTree = ""; }; A6AC89591C5882DD0052AB1C /* BEMGraphCalculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMGraphCalculator.h; sourceTree = ""; }; A6AC895A1C5882DD0052AB1C /* BEMGraphCalculator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMGraphCalculator.m; sourceTree = ""; }; @@ -120,7 +120,7 @@ C3B90A5A187D15F7003E407D /* BEMLine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMLine.h; sourceTree = ""; }; C3B90A5B187D15F7003E407D /* BEMLine.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMLine.m; sourceTree = ""; }; C3B90A5C187D15F7003E407D /* BEMSimpleLineGraphView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BEMSimpleLineGraphView.h; sourceTree = ""; }; - C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BEMSimpleLineGraphView.m; sourceTree = ""; }; + C3B90A5D187D15F7003E407D /* BEMSimpleLineGraphView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = BEMSimpleLineGraphView.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; C3BCA7E61B8ECCA6007E6090 /* CustomizationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomizationTests.m; sourceTree = ""; }; C3BCA7E81B8ECE4E007E6090 /* contantsTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = contantsTests.h; sourceTree = ""; }; C3FD8155186DFD9A00FD8ED3 /* SimpleLineChart.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimpleLineChart.app; sourceTree = BUILT_PRODUCTS_DIR; }; diff --git a/Sample Project/SimpleLineChart/StatsViewController.m b/Sample Project/SimpleLineChart/StatsViewController.m index 5796b48..3623d76 100644 --- a/Sample Project/SimpleLineChart/StatsViewController.m +++ b/Sample Project/SimpleLineChart/StatsViewController.m @@ -17,7 +17,6 @@ @implementation StatsViewController - (void)viewDidLoad { [super viewDidLoad]; - // Do any additional setup after loading the view. } - (void)viewWillAppear:(BOOL)animated { diff --git a/Sample Project/TestBed/AppDelegate.m b/Sample Project/TestBed/AppDelegate.m index 4a8e8f9..2ebc00f 100644 --- a/Sample Project/TestBed/AppDelegate.m +++ b/Sample Project/TestBed/AppDelegate.m @@ -16,7 +16,6 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. return YES; } @@ -28,32 +27,4 @@ - (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:( return YES; } - -- (void)applicationWillResignActive:(UIApplication *)application { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. -} - - -- (void)applicationDidEnterBackground:(UIApplication *)application { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. -} - - -- (void)applicationWillEnterForeground:(UIApplication *)application { - // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. -} - - -- (void)applicationDidBecomeActive:(UIApplication *)application { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. -} - - -- (void)applicationWillTerminate:(UIApplication *)application { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - - @end diff --git a/Sample Project/TestBed/DetailViewController.m b/Sample Project/TestBed/DetailViewController.m index 6987f20..d4c6a50 100644 --- a/Sample Project/TestBed/DetailViewController.m +++ b/Sample Project/TestBed/DetailViewController.m @@ -141,7 +141,7 @@ - (void) removePointFromGraph { [self.myGraph reloadGraph]; } } --(NSString *) formatNumber: (NSNumber *) number { +- (NSString *)formatNumber: (NSNumber *) number { return [NSNumberFormatter localizedStringFromNumber:number numberStyle:NSNumberFormatterDecimalStyle]; @@ -176,7 +176,7 @@ - (CGFloat)lineGraph:(BEMSimpleLineGraphView *)graph valueForPointAtIndex:(NSUIn #pragma mark - SimpleLineGraph Delegate --(BOOL) respondsToSelector:(SEL)aSelector { +- (BOOL)respondsToSelector:(SEL)aSelector { if (aSelector == @selector(popUpTextForlineGraph:atIndex:)) { return self.popUpText.length > 0; } else if (aSelector == @selector(popUpPrefixForlineGraph:)) { @@ -234,7 +234,7 @@ - (NSString *)popUpPrefixForlineGraph:(BEMSimpleLineGraphView *)graph { return self.popUpPrefix; } --(NSString *) popUpTextForlineGraph:(BEMSimpleLineGraphView *)graph atIndex:(NSUInteger)index { +- (NSString *)popUpTextForlineGraph:(BEMSimpleLineGraphView *)graph atIndex:(NSUInteger)index { if (!self.popUpText) return @"Empty format string"; @try { #pragma clang diagnostic push @@ -349,7 +349,7 @@ - (void)lineGraph:(BEMSimpleLineGraphView *)graph didReleaseTouchFromGraphWithCl }]; } --(void) updateLabelsBelowGraph: (BEMSimpleLineGraphView *)graph { +- (void)updateLabelsBelowGraph: (BEMSimpleLineGraphView *)graph { if (self.arrayOfValues.count > 0) { NSNumber * sum = [[BEMGraphCalculator sharedCalculator] calculatePointValueSumOnGraph:graph]; self.labelValues.text =[self formatNumber:sum]; diff --git a/Sample Project/TestBed/MasterViewController.m b/Sample Project/TestBed/MasterViewController.m index d63e191..e952d7e 100644 --- a/Sample Project/TestBed/MasterViewController.m +++ b/Sample Project/TestBed/MasterViewController.m @@ -28,7 +28,7 @@ @interface MasterViewController () = NSNotFound ) { @@ -38,7 +38,7 @@ -(void) setFloatValue:(CGFloat) num { } } --(void) setIntValue:(NSUInteger) num { +- (void)setIntValue:(NSUInteger) num { if (num == NSNotFound ) { self.text = @""; } else if (num == (NSUInteger)-1 ) { @@ -48,7 +48,7 @@ -(void) setIntValue:(NSUInteger) num { } } --(CGFloat) floatValue { +- (CGFloat)floatValue { if (self.text.length ==0) { return -1.0; } else { @@ -56,7 +56,7 @@ -(CGFloat) floatValue { } } --(NSUInteger) intValue { +- (NSUInteger)intValue { if (self.text.length ==0) { return NSNotFound; } else { @@ -75,11 +75,11 @@ @implementation UIButton (Switch) static NSString * checkOff = @"☐"; static NSString * checkOn = @"☒"; --(void) setOn: (BOOL) on { +- (void)setOn: (BOOL) on { [self setTitle: (on ? checkOn : checkOff) forState:UIControlStateNormal]; } --(BOOL) on { +- (BOOL)on { if (!self.currentTitle) return NO; return [checkOff isEqualToString: ( NSString * _Nonnull )self.currentTitle ]; } @@ -203,17 +203,17 @@ - (void)viewDidLoad { self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; } --(void) viewWillAppear:(BOOL)animated { +- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (!self.hasRestoredUI) [self restoreUI]; } --(void) decodeRestorableStateWithCoder:(NSCoder *)coder { +- (void)decodeRestorableStateWithCoder:(NSCoder *)coder { [super decodeRestorableStateWithCoder:coder]; [self restoreUI]; //kludge for VWA not getting called during restore } --(void) restoreUI { +- (void)restoreUI { [self.detailViewController loadViewIfNeeded]; self.myGraph = self.detailViewController.myGraph; self.hasRestoredUI = YES; @@ -337,7 +337,7 @@ - (IBAction)interpolateNullValues:(UISwitch *)sender { } #pragma mark Axes and Reference Lines --(NSUInteger) getValue:(NSString *) text { +- (NSUInteger)getValue:(NSString *) text { return (text.length > 0 && text.integerValue >= 0) ? text.integerValue : NSNotFound; } @@ -452,7 +452,7 @@ - (IBAction)enableReferenceYAxisLines:(UISwitch *)sender { [self.myGraph reloadGraph]; } --(void) updateReferenceAxisFrame: (BOOL) newState { +- (void)updateReferenceAxisFrame: (BOOL) newState { self.myGraph.enableReferenceAxisFrame = newState; self.frameReferenceAxesCell.alpha = newState ? 1.0 : 0.5 ; self.frameReferenceAxesCell.userInteractionEnabled = newState; @@ -569,7 +569,7 @@ - (IBAction)noDataLabelTextDidChange:(UITextField *)sender { // BEMLineAnimationNone //}; // --(void) updateAnimationGraphStyle { +- (void)updateAnimationGraphStyle { NSString * newTitle = @""; switch (self.myGraph.animationGraphStyle) { case BEMLineAnimationDraw: @@ -634,7 +634,7 @@ - (IBAction)fontFamily:(UIButton *)sender { // done in IB: [self performSegueWithIdentifier:@"FontPicker" sender:self]; } --(void) updateFont: (NSString *) fontName { +- (void)updateFont: (NSString *) fontName { if (!fontName) fontName = self.fontNameButton.titleLabel.text; CGFloat fontSize = (CGFloat)self.fontSizeField.text.floatValue; if (fontSize < 1.0) fontSize = 14.0; @@ -667,7 +667,7 @@ - (IBAction)numberFormatChanged:(UITextField *)sender { self.myGraph.formatStringForValues = [self checkUsersFormatString:sender]; [self.myGraph reloadGraph]; } --(NSString *) checkUsersFormatString: (UITextField *) sender { +- (NSString *)checkUsersFormatString: (UITextField *) sender { //there are many ways to crash this (more than one format), but this is most obvious NSString * newFormat = sender.text ?: @""; if ([newFormat containsString:@"%@"]) { @@ -679,7 +679,7 @@ -(NSString *) checkUsersFormatString: (UITextField *) sender { return newFormat; } --(IBAction) alphaTopFieldChanged:(UITextField *) sender { +- (IBAction)alphaTopFieldChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaTop = newAlpha; @@ -687,7 +687,7 @@ -(IBAction) alphaTopFieldChanged:(UITextField *) sender { } } --(IBAction) alphaBottomFieldChanged:(UITextField *) sender { +- (IBAction)alphaBottomFieldChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaBottom = newAlpha; @@ -695,7 +695,7 @@ -(IBAction) alphaBottomFieldChanged:(UITextField *) sender { } } --(IBAction) alphaLineFieldChanged:(UITextField *) sender { +- (IBAction)alphaLineFieldChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaLine = newAlpha; @@ -703,7 +703,7 @@ -(IBAction) alphaLineFieldChanged:(UITextField *) sender { } } --(IBAction) alphaTouchInputFieldChanged:(UITextField *) sender { +- (IBAction)alphaTouchInputFieldChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaTouchInputLine = newAlpha; @@ -711,7 +711,7 @@ -(IBAction) alphaTouchInputFieldChanged:(UITextField *) sender { } } --(IBAction) alphaBackgroundXaxisChanged:(UITextField *) sender { +- (IBAction)alphaBackgroundXaxisChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaBackgroundXaxis = newAlpha; @@ -719,7 +719,7 @@ -(IBAction) alphaBackgroundXaxisChanged:(UITextField *) sender { } } --(IBAction) alphaBackgroundYaxisChanged:(UITextField *) sender { +- (IBAction)alphaBackgroundYaxisChanged:(UITextField *) sender { float newAlpha = sender.floatValue; if (newAlpha >= 0 && newAlpha <= 1.0) { self.myGraph.alphaBackgroundYaxis = newAlpha; @@ -728,7 +728,7 @@ -(IBAction) alphaBackgroundYaxisChanged:(UITextField *) sender { } #pragma Color section --(void) didChangeColor: (UIColor *) color { +- (void)didChangeColor: (UIColor *) color { if (![color isEqual:self.currentColorChip.backgroundColor]) { self.currentColorChip.backgroundColor = color; [self.myGraph setValue: color forKey: self.currentColorKey]; @@ -740,7 +740,7 @@ - (void)colorViewController:(MSColorSelectionViewController *)colorViewCntroller [self didChangeColor:color]; } --(void) saveColor:(id) sender { +- (void)saveColor:(id) sender { self.myGraph.animationGraphStyle = self.saveAnimationSetting; [self dismissViewControllerAnimated:YES completion:nil]; } @@ -897,7 +897,7 @@ - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *) return NO; } --(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { [tableView deselectRowAtIndexPath:indexPath animated:NO]; if (self.splitViewController.isCollapsed) { [self performSegueWithIdentifier:@"showDetail" sender:self]; @@ -906,7 +906,7 @@ -(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath #pragma mark TextDelegate --(BOOL) textFieldShouldReturn:(UITextField *)textField { +- (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } diff --git a/Sample Project/TestBed/StatsViewController.m b/Sample Project/TestBed/StatsViewController.m index 9c1f934..bb90e14 100644 --- a/Sample Project/TestBed/StatsViewController.m +++ b/Sample Project/TestBed/StatsViewController.m @@ -21,7 +21,7 @@ - (void)viewDidLoad { self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(exit:)]; } --(void) exit: (id) sender { +- (void)exit: (id) sender { [self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; } From c107658463bdb98b5cbaa3a82dca6a9ec6258801 Mon Sep 17 00:00:00 2001 From: Hugh Mackworth Date: Sun, 14 May 2017 09:58:54 -0700 Subject: [PATCH 10/15] Further fixes for more substantive comments on PR #295 Fix top reference line offset bug Adds categories for UIKit extensions in MasterVC Adds Options title for iPhone version of TestBed Only changes in storyboard due to newer IB --- Classes/BEMLine.m | 2 +- .../SimpleLineChart.xcodeproj/project.pbxproj | 12 ++ .../TestBed/Base.lproj/Main.storyboard | 184 +++++++++--------- Sample Project/TestBed/MasterViewController.m | 71 +------ Sample Project/TestBed/UIButton+Switch.h | 14 ++ Sample Project/TestBed/UIButton+Switch.m | 24 +++ Sample Project/TestBed/UITextField+Numbers.h | 16 ++ Sample Project/TestBed/UITextField+Numbers.m | 50 +++++ 8 files changed, 212 insertions(+), 161 deletions(-) create mode 100644 Sample Project/TestBed/UIButton+Switch.h create mode 100644 Sample Project/TestBed/UIButton+Switch.m create mode 100644 Sample Project/TestBed/UITextField+Numbers.h create mode 100644 Sample Project/TestBed/UITextField+Numbers.m diff --git a/Classes/BEMLine.m b/Classes/BEMLine.m index 6abf8c5..a6f5b2d 100644 --- a/Classes/BEMLine.m +++ b/Classes/BEMLine.m @@ -73,7 +73,7 @@ - (void)drawRect:(CGRect)rect { if (self.enableTopReferenceFrameLine) { // Top Line - [referenceFramePath moveToPoint: CGPointMake(0+offset, offset)]; + [referenceFramePath moveToPoint: CGPointMake(0, offset)]; [referenceFramePath addLineToPoint:CGPointMake(self.frame.size.width, offset)]; } diff --git a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj index c893261..29cdcc1 100644 --- a/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj +++ b/Sample Project/SimpleLineChart.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 1E03A0321EC8BED200CA4247 /* UITextField+Numbers.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E03A0311EC8BED200CA4247 /* UITextField+Numbers.m */; }; + 1E03A0351EC8BF4C00CA4247 /* UIButton+Switch.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E03A0341EC8BF4C00CA4247 /* UIButton+Switch.m */; }; 1E960A851E7DF942000E2BB8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A841E7DF942000E2BB8 /* main.m */; }; 1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E960A871E7DF942000E2BB8 /* AppDelegate.m */; }; 1E960A8E1E7DF942000E2BB8 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E960A8C1E7DF942000E2BB8 /* Main.storyboard */; }; @@ -68,6 +70,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1E03A0301EC8BED200CA4247 /* UITextField+Numbers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITextField+Numbers.h"; sourceTree = ""; }; + 1E03A0311EC8BED200CA4247 /* UITextField+Numbers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITextField+Numbers.m"; sourceTree = ""; }; + 1E03A0331EC8BF4C00CA4247 /* UIButton+Switch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIButton+Switch.h"; sourceTree = ""; }; + 1E03A0341EC8BF4C00CA4247 /* UIButton+Switch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIButton+Switch.m"; sourceTree = ""; }; 1E960A811E7DF942000E2BB8 /* TestBed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestBed.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1E960A841E7DF942000E2BB8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1E960A861E7DF942000E2BB8 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -187,6 +193,10 @@ 1E960A9B1E7DF9BB000E2BB8 /* DetailViewController.m */, 1E960A9C1E7DF9BB000E2BB8 /* MasterViewController.h */, 1E960A9D1E7DF9BB000E2BB8 /* MasterViewController.m */, + 1E03A0331EC8BF4C00CA4247 /* UIButton+Switch.h */, + 1E03A0341EC8BF4C00CA4247 /* UIButton+Switch.m */, + 1E03A0301EC8BED200CA4247 /* UITextField+Numbers.h */, + 1E03A0311EC8BED200CA4247 /* UITextField+Numbers.m */, 1E960A9E1E7DF9BB000E2BB8 /* StatsViewController.h */, 1E960A9F1E7DF9BC000E2BB8 /* StatsViewController.m */, 1E960AFB1E7F9C80000E2BB8 /* MSColorPicker */, @@ -482,10 +492,12 @@ 1E960AA81E7E0990000E2BB8 /* BEMGraphCalculator.m in Sources */, 1E960AA01E7DF9BC000E2BB8 /* ARFontPickerViewController.m in Sources */, 1E960B171E7F9C80000E2BB8 /* MSHSBView.m in Sources */, + 1E03A0351EC8BF4C00CA4247 /* UIButton+Switch.m in Sources */, 1E960B121E7F9C80000E2BB8 /* MSColorComponentView.m in Sources */, 1E960A881E7DF942000E2BB8 /* AppDelegate.m in Sources */, 1E960AA31E7DF9BC000E2BB8 /* StatsViewController.m in Sources */, 1E960AA21E7DF9BC000E2BB8 /* MasterViewController.m in Sources */, + 1E03A0321EC8BED200CA4247 /* UITextField+Numbers.m in Sources */, 1E960B141E7F9C80000E2BB8 /* MSColorSelectionViewController.m in Sources */, 1E960B161E7F9C80000E2BB8 /* MSColorWheelView.m in Sources */, 1E960A851E7DF942000E2BB8 /* main.m in Sources */, diff --git a/Sample Project/TestBed/Base.lproj/Main.storyboard b/Sample Project/TestBed/Base.lproj/Main.storyboard index 551eec8..4d2f2a0 100644 --- a/Sample Project/TestBed/Base.lproj/Main.storyboard +++ b/Sample Project/TestBed/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -351,7 +351,7 @@ - + @@ -384,7 +384,7 @@ - + @@ -421,7 +421,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -495,7 +495,7 @@ - + @@ -532,7 +532,7 @@ - + @@ -565,7 +565,7 @@ - + @@ -598,7 +598,7 @@ - + @@ -635,7 +635,7 @@ - + @@ -672,7 +672,7 @@ - + @@ -709,7 +709,7 @@ - + @@ -746,7 +746,7 @@ - + @@ -783,7 +783,7 @@ - + @@ -820,7 +820,7 @@ - + @@ -861,7 +861,7 @@ - + @@ -891,7 +891,7 @@ - + @@ -928,7 +928,7 @@ - + @@ -969,7 +969,7 @@ - + @@ -1006,7 +1006,7 @@ - + @@ -1040,7 +1040,7 @@ - + @@ -1074,7 +1074,7 @@ - + @@ -1104,7 +1104,7 @@ - + @@ -1206,7 +1206,7 @@ - + @@ -1239,7 +1239,7 @@ - + @@ -1272,7 +1272,7 @@ - + @@ -1309,7 +1309,7 @@ - + @@ -1342,7 +1342,7 @@ - + @@ -1375,7 +1375,7 @@ - + @@ -1408,7 +1408,7 @@ - + @@ -1445,7 +1445,7 @@ - + @@ -1482,7 +1482,7 @@ - + @@ -1519,7 +1519,7 @@ - + @@ -1552,7 +1552,7 @@ - + @@ -1585,7 +1585,7 @@ - + @@ -1626,7 +1626,7 @@ - + @@ -1657,7 +1657,7 @@ - + @@ -1694,7 +1694,7 @@ - + @@ -1727,7 +1727,7 @@ - + @@ -1760,7 +1760,7 @@ - + @@ -1801,7 +1801,7 @@ - + @@ -1835,7 +1835,7 @@ - + @@ -1872,7 +1872,7 @@ - + @@ -1913,7 +1913,7 @@ - + @@ -1950,7 +1950,7 @@ - + @@ -1983,7 +1983,7 @@ - + @@ -2020,7 +2020,7 @@ - + @@ -2057,7 +2057,7 @@ - + @@ -2090,7 +2090,7 @@ - + @@ -2127,7 +2127,7 @@ - + @@ -2164,7 +2164,7 @@ - + @@ -2197,7 +2197,7 @@ - + @@ -2230,7 +2230,7 @@ - + @@ -2267,7 +2267,7 @@ - + @@ -2304,7 +2304,7 @@ - + @@ -2341,7 +2341,7 @@ - + @@ -2378,7 +2378,7 @@ - + @@ -2415,7 +2415,7 @@ - + @@ -2452,7 +2452,7 @@ - + @@ -2489,7 +2489,7 @@ - + @@ -2526,7 +2526,7 @@ - + @@ -2563,7 +2563,7 @@ - + @@ -2600,7 +2600,7 @@ - + @@ -2736,7 +2736,7 @@ - + @@ -2796,18 +2796,18 @@ - +