How to change the highlight sytle of selected slice in PieChart of ios-charts - ios-charts

ios-charts is a cool library I'm using recently.
This is the expected highlighting slice:
This is my current highlighting effect:
The difference is the highlighted effect is too much than I expected.
I wonder which method can I use to adjust the highlighting effect?

Thanks for #Wingzero's answer.
However I do not want to change the source code or override some methods of it.
Finally, I found the property for the highlight effect of selected slice.
It is a property of #interface PieChartDataSet : ChartDataSet.
/// indicates the selection distance of a pie slice
#property (nonatomic) CGFloat selectionShift;
By setting it as dataSet.selectionShift = 7.0;, it works for my design.
Here attaches the object of dataSet:
PieChartDataSet *dataSet = [[PieChartDataSet alloc] initWithYVals:yVals1 label:NULL];
dataSet.selectionShift = 7.0;
PieChartData *data = [[PieChartData alloc] initWithXVals:nil dataSet:dataSet];
self.pieChartView.data = data;
This is the effect what I have expected.

You can change yourself:
In PieChartRenderer.swift:
public override func drawHighlighted(#context: CGContext, indices: [ChartHighlight])
{
if (_chart.data === nil)
{
return
}
CGContextSaveGState(context)
var rotationAngle = _chart.rotationAngle
var angle = CGFloat(0.0)
var drawAngles = _chart.drawAngles
var absoluteAngles = _chart.absoluteAngles
var innerRadius = drawHoleEnabled && holeTransparent ? _chart.radius * holeRadiusPercent : 0.0
...
}
You can change innerRadius or holeTransparent whatever you like to try.

Related

How store a tuple for NSGradient(colorsAndLocations:) in swift

To apply a gradient to a NSView, it seems best to make a class that inherits from NSView and override the draw method that sets a path and draws the gradient.
class GradientView: NSView {
var startColor = NSColor.red
var startPosition: CGFloat = 0
var endColor = NSColor.white
var endPosition: CGFloat = 1
var rotation: CGFloat = 0
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))
let path = NSBezierPath.init(rect: self.bounds)
bgGradient?.draw(in: path, angle: rotation)
}
}
(Let me know if there's a better/easier way...)
It works great, but now I want to send a custom collection of colors & color-stops. Note: *With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.
I'd like to use (an array ? of) tuples (containing several colors and their stop values). The API to set a color and location is NSGradient(colorsAndLocations:) and uses the tuple (NSColor, CGFloat).
I can add a parameter like var colorStopArray: [(color: NSColor, stop: CGFloat)] = [] that creates an array of (such) tuples. Also I can append values with: colorStopArray.append((startColor, startPosition)).
But how can I assign this to the API?
I've tried many methods and they all cause errors (eg. tried NSGradient(colorsAndLocations: colorStopArray.flatMap{return ($0.color, $0.stop)}) )
So how can send many custom colors+locations to the above class (or hopefully a better suggestion) to create a custom gradient (which will then be drawn on my NSView)?
With this above method, I am limited to defining two (the startColor & startPosition and endColor & endPosition) only.
No, you're not. This is a variadic; you can add as many tuples as you wish. This works fine:
let startColor = NSColor.red
let startPosition: CGFloat = 0
let middleColor = NSColor.blue
let middlePosition: CGFloat = 0.5
var endColor = NSColor.white
var endPosition: CGFloat = 1
let bgGradient = NSGradient(colorsAndLocations:
(startColor, startPosition),
(middleColor, middlePosition),
(endColor, endPosition))
If the question is "can I turn an array into a variadic?", then the answer is, unfortunately no. This feature ("splatting") is missing from the Swift language. The only way to call a variadic in Swift is as a variadic.
The initializer that lets you supply an array of colors and stop locations is init(colors:atLocations:colorSpace:). So if that's what you want to supply, call that initializer instead.
Using matt's clues, I replaced the let line (using the more appropriate API init(colors:atLocations:colorSpace:)) :
let bgGradient = NSGradient(colorsAndLocations: (startColor, startPosition), (endColor, endPosition))
with
let bgGradient = NSGradient(colors: colorStopArray.map { $0.color }, atLocations: colorStopArray.map { $0.stop }, colorSpace: NSColorSpace.genericRGB)
Now it functions as I desired.

Swift: Cant change CALayer background color in array

I am simply trying to change the background color of the last element of a CALayer array. Here is my entire View Class, however its only 2-3 lines that I actually try to access the last element of the CALayer.
Here is my progressViewClass and I put comments to where exactly my problem is:
class ProgressBarView: UIView {
//Variables for progress bar
var holdGesture = UILongPressGestureRecognizer()
let animation = CABasicAnimation(keyPath: "bounds.size.width")
var layerHolder = [CALayer]()
var widthIndex = CGPoint(x: 0, y: 0)
var nextXOffset = CGFloat(0.0)
var checkIfFull = CGFloat()
var newLayer : CALayer?
var progressBarAnimationDuration : CFTimeInterval = (MainController.sharedInstance.totalMiliSeconsToRecord / 10)
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
}
func startProgressBar(){
if(RecordingViewController().currentCameraMode == .recordingMode || RecordingViewController().currentCameraMode == .record1stClipMode) {
newLayer = CALayer()
newLayer?.frame = CGRect(x: nextXOffset, y: 0, width: 0, height: self.bounds.height)
newLayer?.backgroundColor = UIColor(red:0.82, green:0.01, blue:0.11, alpha:1.0).cgColor
//print("before \(nextXOffset)")
newLayer?.anchorPoint = widthIndex
animation.fromValue = 0
animation.toValue = self.bounds.width - nextXOffset
animation.duration = progressBarAnimationDuration - ((MainController.sharedInstance.miliSecondsPassed) / 10)
self.layer.addSublayer(newLayer!)
//print("Long Press Began")
newLayer?.add(animation, forKey: "bounds.size.width")
}
else{
stopProgressBar()
}
}
func stopProgressBar(){
if(RecordingViewController().currentCameraMode != .recordingMode){
pauseLayer(layer: newLayer!)
newLayer?.frame = (newLayer?.presentation()!.frame)!
nextXOffset = (newLayer?.frame.maxX)!
layerHolder.append(newLayer!)
print("Layerholder has elements : \(layerHolder.count)")
}
}
// HERE IS MY PROBLEM
func highlightLastLayer(){
print("in highlight last layer Layerholder has elements : \(layerHolder.count)")
// I CAN HIDE THE CALAYER SO I BELIEVE IM ACCESSING THE CORRECT LAYER
// layerHolder.last?.isHidden = true
// This is suppose to change the last element background color to blue but doesnt
layerHolder.last?.backgroundColor = UIColor.blue.cgColor
}
// ALSO MY PROBLEM
func unhighlightLastLayer(){
print("inside unhighlight last layer")
// I CAN HIDE THE CALAYER SO I BELIEVE IM ACCESSING THE CORRECT LAYER
//layerHolder.last?.isHidden = false
// Changes CALayer back to red
layerHolder.last?.backgroundColor = UIColor(red:0.82, green:0.01, blue:0.11, alpha:1.0).cgColor
}
//Function to pause the Progress Bar
func pauseLayer(layer : CALayer){
let pausedTime : CFTimeInterval = layer.convertTime(CACurrentMediaTime(), from: nil)
layer.speed = 0.0
layer.timeOffset = pausedTime
}
}
Simply put, I create a progressView object in my viewController and then call those functions based on certain button input. This view is essentially a progress bar that you'd see in many video recording applications to show how much you have recorded. In the highlightLastLayer, I am trying to grab the last element of the "layerHolder" array and change its color to blue. Simple right? Doesn't work. Any ideas?
Where are you calling highlight and unhighlight. I am pretty sure you are doing this when you are "stopped" or "paused" because thats the only time you add anything to the layerHolder Array. You can only do this when you are not animating because when you are animating the presentation layer is shown instead of the "real" layer. Instead of setting the speed to zero, setup your layer to look like the current animation state and call layer. removeAllAnimations to kill the presentation layer and show the actual layer for your view instead. Now you can make all the changes that you want and they will actually show up.

NSTextField not drawing correctly in custom NSView

I have a custom NSView and I place three NSTextFields into the view programmatically (axes labels for a graph, basically):
var axesTextFields : Array<NSTextField> = [NSTextField(),NSTextField(),NSTextField()]
func addTextFields() {
axesTextFields[0].stringValue = "x"
axesTextFields[1].stringValue = "y"
axesTextFields[2].stringValue = "z"
for tf in axesTextFields {
tf.textColor = NSColor.blackColor()
tf.bezeled = false
tf.editable = false
tf.drawsBackground = false
self.addSubview(tf)
}
}
I update the location of the NSTextFields in the drawRect() function:
override func drawRect(fullRect: NSRect)
{
... // drawing lines for the graph
axesTextFields[0].frame = NSMakeRect(0,axes_location * 3 - 10,20,20)
axesTextFields[1].frame = NSMakeRect(0,axes_location * 2 - 10,20,20)
axesTextFields[2].frame = NSMakeRect(0,axes_location - 10,20,20)
}
The view sometimes draws correctly, but many times it looks like this (with the NSTextFields corrupted on the left):
When I drag the window to resize it, it gets drawn correctly most of the time:
I'm not sure where I'm going wrong.

Create PDF of dynamic size with typography using UIView template(s)

I'm new but have managed to learn a lot and create a pretty awesome (I hope) app that's near completion. One of my last tasks is to create a PDF of dynamically generated user data. It has been the most frustrating part of this whole process as there is no real modern clear cut template or guide. Apple's documentation isn't very descriptive (and some parts I don't understand) and the Q/A here on stack and examples on Google all seem very case specific. I thought I almost had it by using a UIScrollView but the end result was messy and I couldn't get things to line up neat enough, nor did I have enough control.
I believe my flaws with this task are logic related and not knowing enough about available APIs, help on either is greatly appreciated.
I have dynamically created user content filling an NSArray in a subclass of a UIViewController. That content consists of Strings and Images.
I would like to use a UIView (I'm presuming in a .xib file) to create a template with header information for the first page of the PDF (the header information is dynamic as well), any page after that can be done via code as it really is just a list.
I have a small understanding of UIGraphicsPDF... and would like to draw the text and images into the PDF and not just take a screen shot of the view.
My trouble with getting this going is:
(I'm being basic here on purpose because what I have done so far has led me nowhere)
How do I find out how many pages I'm going to need?
How do I find out if text is longer than a page and how do I split it?
How do I draw images in the PDF?
How do I draw text in the PDF?
How do I draw both text and images in the PDF but padded vertically so there's no overlap and account for Strings with a dynamic number of lines?
How do I keep track of the pages?
Thank you for reading, I hoe the cringe factor wasn't too high.
So here we go. The following was made for OSX with NSView but it's easily adapatable for UIView (so I guess). You will need the following scaffold:
A) PSPrintView will handle a single page to print
class PSPrintView:NSView {
var pageNo:Int = 0 // the current page
var totalPages:Int = 0
struct PaperDimensions {
size:NSSize // needs to be initialized from NSPrintInfo.sharedPrintInfo
var leftMargin, topMargin, rightMargin, bottomMargin : CGFloat
}
let paperDimensions = PaperDimensions(...)
class func clone() -> PSPrintView {
// returns a clone of self where most page parameters are copied
// to speed up printing
}
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// Drawing code here.
// e.g. to draw a frame inside the view
let scale = convertSize(NSMakeSize(1, 1), fromView:nil)
var myContext = NSGraphicsContext.currentContext()!.CGContext
CGContextSetLineWidth(myContext, scale.height)
CGContextSetFillColorWithColor(myContext, NSColor.whiteColor().CGColor)
CGContextFillRect (myContext, rect)
rect.origin.x += paperDimensions.leftMargin
rect.origin.y += paperDimensions.bottomMargin
rect.size.width -= paperDimensions.leftMargin + paperDimensions.rightMargin
rect.size.height -= paperDimensions.topMargin + paperDimensions.bottomMargin
CGContextSetStrokeColorWithColor(myContext, NSColor(red: 1, green: 0.5, blue: 0, alpha: 0.5).CGColor)
CGContextStrokeRect(myContext, rect)
// here goes your layout with lots of String.drawInRect....
}
}
B) PSPrint: will hold the single PSPrintViews in an array and when done send them to the (PDF) printer
class PSPrint: NSView {
var printViews = [PSPrintView]()
override func knowsPageRange(range:NSRangePointer) -> Bool {
range.memory.location = 1
range.memory.length = printViews.count
return true
}
func printTheViews() {
let sharedPrintInfo = NSPrintInfo.sharedPrintInfo()
let numOfViews = printViews.count
var totalHeight:CGFloat = 0;//if not initialized to 0 weird problems occur after '3' clicks to print
var heightOfView:CGFloat = 0
// PSPrintView *tempView;
for tempView in printViews {
heightOfView = tempView.frame.size.height
totalHeight = totalHeight + heightOfView
}
//Change the frame size to reflect the amount of pages.
var newsize = NSSize()
newsize.width = sharedPrintInfo.paperSize.width-sharedPrintInfo.leftMargin-sharedPrintInfo.rightMargin
newsize.height = totalHeight
setFrameSize(newsize)
var incrementor = -1 //default the incrementor for the loop below. This controls what page a 'view' will appear on.
//Add the views in reverse, because the Y position is bottom not top. So Page 3 will have y coordinate of 0. Doing this so order views is placed in array reflects what is printed.
for (var i = numOfViews-1; i >= 0; i--) {
incrementor++
let tempView = printViews[i] //starts with the last item added to the array, in this case rectangles, and then does circle and square.
heightOfView = tempView.frame.size.height
tempView.setFrameOrigin(NSMakePoint(0, heightOfView*CGFloat(incrementor))) //So for the rectangle it's placed at position '0', or the very last page.
addSubview(tempView)
}
NSPrintOperation(view: self, printInfo: sharedPrintInfo).runOperation()
}
C) a function to perform printing (from the menu)
func doPrinting (sender:AnyObject) {
//First get the shared print info object so we know page sizes. The shared print info object acts like a global variable.
let sharedPrintInfo = NSPrintInfo.sharedPrintInfo()
//initialize it's base values.
sharedPrintInfo.leftMargin = 0
sharedPrintInfo.rightMargin = 0
sharedPrintInfo.topMargin = 0
sharedPrintInfo.bottomMargin = 0
var frame = NSRect(x: 0, y: 0, width: sharedPrintInfo.paperSize.width-sharedPrintInfo.leftMargin-sharedPrintInfo.rightMargin, height: sharedPrintInfo.paperSize.height-sharedPrintInfo.topMargin-sharedPrintInfo.bottomMargin)
//Initiate the printObject without a frame, it's frame will be decided later.
let printObject = PSPrint ()
//Allocate a new instance of NSView into the variable printPageView
let basePrintPageView = PSPrintView(frame: frame)
// do additional init stuff for the single pages if needed
// ...
var printPageView:PSPrintView
for pageNo in 0..<basePrintPageView.totalPages {
printPageView = basePrintPageView.clone()
//Set the option for the printView for what it should draw.
printPageView.pageNo = pageNo
//Finally append the view to the PSPrint Object.
printObject.printViews.append(printPageView)
}
printObject.printTheViews() //print all the views, each view being a 'page'.
}
The PDF drawing code:
import UIKit
class CreatePDF {
// Create a PDF from an array of UIViews
// Return a URL of a temp dir / pdf file
func getScaledImageSize(imageView: UIImageView) -> CGSize {
var scaledWidth = CGFloat(0)
var scaledHeight = CGFloat(0)
let image = imageView.image!
if image.size.height >= image.size.width {
scaledHeight = imageView.frame.size.height
scaledWidth = (image.size.width / image.size.height) * scaledHeight
if scaledWidth > imageView.frame.size.width {
let diff : CGFloat = imageView.frame.size.width - scaledWidth
scaledHeight = scaledHeight + diff / scaledHeight * scaledHeight
scaledWidth = imageView.frame.size.width
}
} else {
scaledWidth = imageView.frame.size.width
scaledHeight = (image.size.height / image.size.width) * scaledWidth
if scaledHeight > imageView.frame.size.height {
let diff : CGFloat = imageView.frame.size.height - scaledHeight
scaledWidth = scaledWidth + diff / scaledWidth * scaledWidth
scaledHeight = imageView.frame.size.height
}
}
return CGSizeMake(scaledWidth, scaledHeight)
}
func drawImageFromUIImageView(imageView: UIImageView) {
let theImage = imageView.image!
// Get the image as it's scaled in the image view
let scaledImageSize = getScaledImageSize(imageView)
let imageFrame = CGRectMake(imageView.frame.origin.x, imageView.frame.origin.y, scaledImageSize.width, scaledImageSize.height)
theImage.drawInRect(imageFrame)
}
func drawTextFromLabel(aLabel: UILabel) {
if aLabel.text?.isEmpty == false {
let theFont = aLabel.font
let theAttributedFont = [NSFontAttributeName: theFont!]
let theText = aLabel.text!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) as NSString
let theFrame = aLabel.frame
theText.drawInRect(theFrame, withAttributes: theAttributedFont)
}
}
func parseSubviews(aView: UIView) {
for aSubview in aView.subviews {
if aSubview.isKindOfClass(UILabel) {
// Draw label
drawTextFromLabel(aSubview as! UILabel)
}
if aSubview.isKindOfClass(UIImageView) {
// Draw image (scaled and at correct coordinates
drawImageFromUIImageView(aSubview as! UIImageView)
}
}
}
func parseViewsToRender(viewsToRender: NSArray) {
for aView in viewsToRender as! [UIView] {
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil)
parseSubviews(aView)
}
}
func createPdf(viewsToRender: NSArray, filename: String) -> NSURL {
// Create filename
let tempDir = NSTemporaryDirectory()
let pdfFilename = tempDir.stringByAppendingPathComponent(filename)
UIGraphicsBeginPDFContextToFile(pdfFilename, CGRectZero, nil)
// begin to render the views in this context
parseViewsToRender(viewsToRender)
UIGraphicsEndPDFContext()
return NSURL(string: pdfFilename)!
}
}
First, I made a xib file with a UIView that fit the dimensions of a single PDF page and for my header information. This size is 612 points wide by 790 points tall.
Then I added UILabels for all of the page 1 header information I want to use (name, address, date, etc.)
I took note of y position and height of the lowest UILabel for my header information and subtracted it from the amount of vertical space in a page.
I also took note of the font and font size I wanted to use.
Then I created a class called CreatePDF
In that class I created several variables and constants, the font name, the font size, the size of a page, the remaining vertical space after header information.
In that class I created a method that takes two different arguments, one is a dictionary that I used for header information, the other is an array of UIImages and Strings.
That method calls a few other methods:
Determine the vertical height required for the items in the array
To do this I created another two methods, one to determine the height of a UILabel with any given string and one to determine the height of an image (vertical and horizontal images having different heights the way that I scale them). They each returned a CGFloat, which I added to a variable in the method that kept track of all the items in array.
For each item that was “sized” I then added another 8 points to use as a vertical offset.
Determine how many pages will be needed
The above method returned a CGFloat that I then used to figure out if either all the items will fit on one page below the header or if another page will be needed, and if so, how many more pages.
Draw a UIView
This method accepts the above mentioned dictionary, array and an estimated number of pages. It returns an array of UIViews.
In this method I create a UIView that matches the size of one PDF Page, I run a loop for each page and add items to it, I check to see if an item will fit by comparing it’s y position and height with the reaming vertical space on a page by subtracting the current Y position from the page height then I add an item and keep track of it’s height and y position, If the remaining height won’t work and I’m out of pages, I add another page.
Send the array to draw a PDF
I create the PDF context here
I take the array of UIViews as an argument, for each view I create a PDF Page in the PDF Context and then iterate through it’s subviews, if it’s a UILabel I send it off to a function that draws the UILabel at it’s frame position with it’s text property as the string. I create an attributed front using the variables defined in the class earlier. If it’s an image I send it to another function that also uses it’s frame, however I have to send it to yet another function to determine the actual dimensions of the image that’s drawn inside the UIImage (it changes based on scaling) and I return that for where to draw the image (this happens above too to properly size it).
That’s pretty much it, in my case I created the PDF context with a file, then end up returning the file to whoever calls this function. The hardest part for me to wrap my head around was keeping track of the vertical positioning.
I’ll work on making the code more generic and post it up somewhere.

Setting tick marks for UISlider

Is there any method for setting tick marks for UISlider. NSSlider has something called
- (void)setNumberOfTickMarks:(NSInteger)numberOfTickMarks. But UISlider does not appear to have one.
What i want to do is get the slider values as 0,1,2,3,4 etc if say i set the min as 0 and max as 5. Now as such UISLider returns something like 0.0,0.1,0.2,0.3 etc 4.8,4.9,5.0 based on the example i gave above.
Any help??
UISlider doesn't have any tickmarks.
To get the behaviour you specify you'll have to round the number returned by the slider to the nearest integer.
So when you start sliding, and the slider reports a value of 0.1 or 0.2, round that to 0. Until it hits 0.5, when you round up to 1.
Then when the slider stops sliding, you can set it's value to the number you rounded to, which will make the slider "snap" to that position, kind like snapping to a tick mark.
If you want visual tickmarks, you'll have to implement that yourself.
Yeah I think that subclassing is the way to go. This is what I did:
TickSlider.h…
/**
* A custom slider that allows the user to specify the number of tick marks.
* It behaves just like the NSSlider with tick marks for a MacOS app.
**/
#interface TickSlider : UISlider
/**
* The number of tickmarks for the slider. No graphics will be draw in this
* sublcass but the value of the slider will be rounded to the nearest tick
* mark in the same way/behaviour as the NSSlider on a MacOS app.
* #discussion This value can be set as a UserDefinedAttribute in the xib file.
**/
#property (nonatomic) NSInteger numberOfTickMarks;
#end
TickSlider.m…
#import "TickSlider.h"
#implementation TickSlider
- (void)setValue:(float)value animated:(BOOL)animated {
float updatedValue = value;
if (self.numberOfTickMarks > 0) {
updatedValue = MAX(self.minimumValue, MIN(value, self.maximumValue));
if (self.numberOfTickMarks == 1) {
updatedValue = (self.minimumValue + self.maximumValue) * 0.5f;
}
else {
const int kNumberOfDivisions = (self.numberOfTickMarks - 1);
const float kSliderRange = ([self maximumValue] - [self minimumValue]);
const float kRangePerDivision = (kSliderRange / kNumberOfDivisions);
const float currentTickMark = roundf(value / kRangePerDivision);
updatedValue = (currentTickMark * kRangePerDivision);
}
}
[super setValue:updatedValue animated:animated];
}
#end
As above, casting to int makes the slider behave as if you where sliding next to the thumb. The above ticker code is interesting, allowing you to specify more "stop values" (ticks) as the maximum value setting.
If you need a simple "integer slider", rounding (value + 0.25) actually makes the slider behave quite nicely. This is the code I use:
#interface IntegerSlider : UISlider
#end
#implementation IntegerSlider
- (void) setValue: (float) value
animated: (BOOL) animated
{
value = roundf(value + 0.25);
[super setValue: value
animated: animated];
}
#end
I just cast [slider value] to int to achieve this.
For anyone needing the Swift 5 version, here it is:
class CAUISlider: UISlider {
// MARK: - Properties
public var numberOfTickMarks: Int = 0
// MARK: - Public
override func setValue(_ value: Float, animated: Bool) {
var updatedValue: Float = value
if numberOfTickMarks > 0 {
updatedValue = max(minimumValue, maximumValue)
}
if numberOfTickMarks == 1 {
updatedValue = (minimumValue + maximumValue) * 0.5
} else {
let kNumberOfDivisions = numberOfTickMarks - 1
let kSliderRange = maximumValue - minimumValue
let kRangePerDivision = kSliderRange / Float(kNumberOfDivisions)
let currentTickMark = roundf(value / kRangePerDivision)
updatedValue = currentTickMark * kRangePerDivision
}
super.setValue(updatedValue, animated: animated)
}
}