Custom X Axis for BarChartView - ios

I am using BarChartView from danielgindi/ios-charts charting library. So far I am able to get following BarChartView. My question, is there any way to Customize X Axis that way, it will only show min (left most value 37.03), max (right most value 37.31 which is hiding), value for green bar (37.17) and value for orange bar (37.18 which is hiding again). In a nutshell I want to hide some value from XAxis which is not important in my case.

This is an example: add a property called showOnlyLeftRightEnabled in YourChartXAxis class, then in your custom x axis renderer, overide internal override func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint).
To display the middle one's you need to figure out which bar it is, and convert the x Index to pixel values just like what I did for the left most and right most value via CGPointApplyAffineTransform(position, valueToPixelMatrix)
internal override func drawLabels(context context: CGContext, pos: CGFloat, anchor: CGPoint)
{
let paraStyle = NSParagraphStyle.defaultParagraphStyle().mutableCopy() as! NSMutableParagraphStyle
paraStyle.alignment = .Center
let labelAttrs = [NSFontAttributeName: _xAxis.labelFont,
NSForegroundColorAttributeName: _xAxis.labelTextColor,
NSParagraphStyleAttributeName: paraStyle]
let labelRotationAngleRadians = _xAxis.labelRotationAngle * ChartUtils.Math.FDEG2RAD
let valueToPixelMatrix = transformer.valueToPixelMatrix
var position = CGPoint(x: 0.0, y: 0.0)
var labelMaxSize = CGSize()
if (_xAxis.isWordWrapEnabled)
{
labelMaxSize.width = _xAxis.wordWrapWidthPercent * valueToPixelMatrix.a
}
if let showOnlyLeftRight = (_xAxis as? YourChartXAxis)?.showOnlyLeftRightEnabled
{
if (showOnlyLeftRight)
{
for (var i = lowestVisibleXIndex, maxX = min(highestVisibleXIndex + 1, _xAxis.values.count); i < maxX; i += maxX - lowestVisibleXIndex - 1)
{
let label = _xAxis.values[i]
if (label == nil)
{
continue
}
position.x = CGFloat(i)
position.y = 0.0
position = CGPointApplyAffineTransform(position, valueToPixelMatrix)
let labelns = label! as NSString
//label location by string width
// fix rightLabel
if (highestVisibleXIndex == lowestVisibleXIndex)
{
return super.drawLabels(context: context, pos: pos, anchor: anchor)
}
else if (i == highestVisibleXIndex)
{
let width = labelns.boundingRectWithSize(labelMaxSize, options: .UsesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
rightLabelX -= width / 2.0
}
else if (i == lowestVisibleXIndex)
{ // fix leftLabel
let width = labelns.boundingRectWithSize(labelMaxSize, options: .UsesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
leftLabelX += width / 2.0
}
// fill leftLabel and rightLabel in current visible screen
if (lowestVisibleXIndex == highestVisibleXIndex) {
ChartUtils.drawMultilineText(context: context, text: label!, point: CGPoint(x: leftLabelX, y: pos), attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
ChartUtils.drawMultilineText(context: context, text: label!, point: CGPoint(x: rightLabelX, y: pos), attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
break
} else if (i == lowestVisibleXIndex) {
ChartUtils.drawMultilineText(context: context, text: label!, point: CGPoint(x: leftLabelX, y: pos), attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
} else if (i == highestVisibleXIndex) {
ChartUtils.drawMultilineText(context: context, text: label!, point: CGPoint(x: rightLabelX, y: pos), attributes: labelAttrs, constrainedToSize: labelMaxSize, anchor: anchor, angleRadians: labelRotationAngleRadians)
}
}
} else {
super.drawLabels(context: context, pos: pos, anchor: anchor)
}
}
}

Related

danielgindi/Charts how to get xLabel position

I was able to get the position of the x Label from xAxisRenderer. But I failed to import this into my project. I'm trying to get this position and create a custom x-axis. How can I store a value in an external module and use it in an internal module?
#objc open func drawLabels(context: CGContext, pos: CGFloat, anchor: CGPoint)
{
guard let transformer = self.transformer else { return }
let paraStyle = ParagraphStyle.default.mutableCopy() as! MutableParagraphStyle
paraStyle.alignment = .center
let labelAttrs: [NSAttributedString.Key : Any] = [.font: axis.labelFont,
.foregroundColor: axis.labelTextColor,
.paragraphStyle: paraStyle]
let labelRotationAngleRadians = axis.labelRotationAngle.DEG2RAD
let isCenteringEnabled = axis.isCenterAxisLabelsEnabled
let valueToPixelMatrix = transformer.valueToPixelMatrix
var position = CGPoint.zero
var labelMaxSize = CGSize.zero
if axis.isWordWrapEnabled
{
labelMaxSize.width = axis.wordWrapWidthPercent * valueToPixelMatrix.a
}
let entries = axis.entries
for i in entries.indices
{
let px = isCenteringEnabled ? CGFloat(axis.centeredEntries[i]) : CGFloat(entries[i])
position = CGPoint(x: px, y: 0)
.applying(valueToPixelMatrix)
guard viewPortHandler.isInBoundsX(position.x) else { continue }
let label = axis.valueFormatter?.stringForValue(axis.entries[i], axis: axis) ?? ""
let labelns = label as NSString
if axis.isAvoidFirstLastClippingEnabled
{
// avoid clipping of the last
if i == axis.entryCount - 1 && axis.entryCount > 1
{
let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
if width > viewPortHandler.offsetRight * 2.0,
position.x + width > viewPortHandler.chartWidth
{
position.x -= width / 2.0
}
}
else if i == 0
{ // avoid clipping of the first
let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width
position.x += width / 2.0
}
}
print(position.x)
drawLabel(context: context,
formattedLabel: label,
x: position.x,
y: pos,
attributes: labelAttrs,
constrainedTo: labelMaxSize,
anchor: anchor,
angleRadians: labelRotationAngleRadians)
}
}
I tried to get the position value by creating an open class called XAxisPosition, but the value is initialized to 0 in the process of importing it into my project module. I have access to it, but how do I get the values ​​fully into my project?
public protocol XAxisRendererDelegate
{
#objc optional func renderer(_ renderer: XAxisRenderer,
labelPostion: CGPoint)
}
#objc open weak var delegate: XAxisRendererDelegate?
I added this XAxisRenderer.
and add this in drawLabels.
let px = isCenteringEnabled ? CGFloat(axis.centeredEntries[i]) :
CGFloat(entries[i])
position = CGPoint(x: px, y: 0)
.applying(valueToPixelMatrix)
delegate?.renderer?(self, labelPostion: position)
and use this
chartView.xAxisRenderer.delegate = self

Wrong highlight annotation on apple PDFKit

I'm using PDFKit on iOS to highlight texts (PDF file). I do it by create a PDFAnnotation and add it to the selected text area. I want to highlight precisely the selected area but it always covers the whole line like the pics below. How can I create the annotation for the selected area only??
My code:
let highlight = PDFAnnotation(bounds: selectionText.bounds(for: page), forType: PDFAnnotationSubtype.highlight, withProperties: nil)
highlight.color = highlightColor
page.addAnnotation(highlight)
As suggested in PDFKit Highlight Annotation: quadrilateralPoints you can use quadrilateralPoints to add different lines highlights to the same annotation.
func highlight() {
guard let selection = pdfView.currentSelection, let currentPage = pdfView.currentPage else {return}
let selectionBounds = selection.bounds(for: currentPage)
let lineSelections = selection.selectionsByLine()
let highlightAnnotation = PDFAnnotation(bounds: selectionBounds, forType: PDFAnnotationSubtype.highlight, withProperties: nil)
highlightAnnotation.quadrilateralPoints = [NSValue]()
for (index, lineSelection) in lineSelections.enumerated() {
let n = index * 4
let bounds = lineSelection.bounds(for: pdfView.currentPage!)
let convertedBounds = bounds.convert(to: selectionBounds.origin)
highlightAnnotation.quadrilateralPoints?.insert(NSValue(cgPoint: convertedBounds.topLeft), at: 0 + n)
highlightAnnotation.quadrilateralPoints?.insert(NSValue(cgPoint: convertedBounds.topRight), at: 1 + n)
highlightAnnotation.quadrilateralPoints?.insert(NSValue(cgPoint: convertedBounds.bottomLeft), at: 2 + n)
highlightAnnotation.quadrilateralPoints?.insert(NSValue(cgPoint: convertedBounds.bottomRight), at: 3 + n)
}
pdfView.currentPage?.addAnnotation(highlightAnnotation)
}
extension CGRect {
var topLeft: CGPoint {
get {
return CGPoint(x: self.origin.x, y: self.origin.y + self.height)
}
}
var topRight: CGPoint {
get {
return CGPoint(x: self.origin.x + self.width, y: self.origin.y + self.height)
}
}
var bottomLeft: CGPoint {
get {
return CGPoint(x: self.origin.x, y: self.origin.y)
}
}
func convert(to origin: CGPoint) -> CGRect {
return CGRect(x: self.origin.x - origin.x, y: self.origin.y - origin.y, width: self.width, height: self.height)
}
}
PDFSelection bounds(forPage:) method returns one rectangle to satisfy whole selection area. Is not the best solution in your case.
Try with selectionsByLine(), and add individual annotation for every rect, representing every single selected line in PDF. Example:
let selections = pdfView.currentSelection?.selectionsByLine()
// Simple scenario, assuming your pdf is single-page.
guard let page = selections?.first?.pages.first else { return }
selections?.forEach({ selection in
let highlight = PDFAnnotation(bounds: selection.bounds(for: page), forType: .highlight, withProperties: nil)
highlight.endLineStyle = .square
highlight.color = UIColor.orange.withAlphaComponent(0.5)
page.addAnnotation(highlight)
})

How to create UITableViewCell with dynamically height change according to array coming from API

Its array data for only one cell view, I need to set product_name and total_quantity on table view cell.sometime total_quantity can be 2 sometime it can be 10. Just give me some basic idea so I can move forward.
[[{"total_quantity":"50","product_id":"13","product_name":"Prawns"},{"total_quantity":"13","product_id":"14","product_name":"Fish"},{"total_quantity":"57","product_id":"15","product_name":"Chicken
Breast"},{"total_quantity":"10","product_id":"16","product_name":"Beef"}],[{"total_quantity":"50","product_id":"13","product_name":"Prawns"},{"total_quantity":"13","product_id":"14","product_name":"Fish"},{"total_quantity":"57","product_id":"15","product_name":"Chicken
Breast"},{"total_quantity":"10","product_id":"16","product_name":"Beef"}]]
Screen shot
extension String {
func sizeWithGivenSize(_ size: CGSize,font: UIFont,paragraph: NSMutableParagraphStyle? = nil) -> CGSize {
var attributes = [String:AnyObject]()
attributes[NSFontAttributeName] = font
if let p = paragraph {
attributes[NSParagraphStyleAttributeName] = p
}
attributes[NSFontAttributeName] = font
return (self as NSString).boundingRect(with: size,
options: [.usesLineFragmentOrigin,.usesFontLeading],
attributes: attributes,
context: nil).size
}
Use this Method to get UILabel's Frame with different length string.
then to calculate Height form top to bottom.
for high performance, you'd better to do this at model.
For example:
func calculateFrame() -> Void {
let width = UIComponentsConst.screenWidth
var tmpFrame = ProjectDetailTeamCellLayout()
var maxX: CGFloat = 0
var maxY: CGFloat = tmpFrame.topSpace
if let titleText = title {
let titleSize = titleText.sizeWithGivenSize(MaxSize, font: ProjectDetailTeamCellLayout.titleFont, paragraph: nil)
tmpFrame.titleLabelFrame = CGRect.init(x: 15, y: maxY, width: titleSize.width, height: titleSize.height)
maxY = tmpFrame.titleLabelFrame!.maxY
maxX = tmpFrame.titleLabelFrame!.maxX
}
if let nameText = nameTitle, !nameText.isEmpty {
tmpFrame.grayLineFrame = CGRect(x: maxX + tmpFrame.itemSpace, y: tmpFrame.topSpace, width: 1, height: tmpFrame.titleLabelFrame?.height ?? 0)
let nameSize = nameText.sizeWithGivenSize(MaxSize, font: ProjectDetailTeamCellLayout.nameLabelFont, paragraph: nil)
let nameTmpFrame = CGRect(x: tmpFrame.grayLineFrame!.maxX + tmpFrame.itemSpace, y: tmpFrame.topSpace + (tmpFrame.titleLabelFrame?.height ?? nameSize.height ) / 2 - nameSize.height / 2, width: nameSize.width, height: nameSize.height)
tmpFrame.nameLabelFrame = nameTmpFrame
maxY = maxY > tmpFrame.nameLabelFrame!.maxY ? maxY : tmpFrame.nameLabelFrame!.maxY
}
if let subTitleText = subTitle, !subTitleText.isEmpty {
let subTitleSize = subTitleText.sizeWithGivenSize(CGSize(width: width - 45, height: CGFloat.greatestFiniteMagnitude), font: ProjectDetailTeamCellLayout.subTitleFont, paragraph: nil)
tmpFrame.subTitleLabelFrame = CGRect(x: 15, y: maxY + tmpFrame.lineSpace, width: subTitleSize.width, height: subTitleSize.height)
maxY = tmpFrame.subTitleLabelFrame!.maxY + 10
}
tmpFrame.height = maxY + 5
self.frameInfo = tmpFrame
}
try ?
Use something like this, if you want to change the height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let object = myArrayDataset[indexPath.row]
//for example
if object.numberOfItems > 10 {
return 80.0
}
else {
return 40.0
}
}

How to define a custom NSUnderlineStyle

Looking at the documentation for NSLayoutManager, specifically the drawUnderlineForGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin: method, I noticed the following (emphasis mine):
underlineVal
The style of underlining to draw. This value is a mask derived from the value for NSUnderlineStyleAttributeName—for example, (NSUnderlinePatternDash | NSUnderlineStyleThick). Subclasses can define custom underlining styles.
My question is: how exactly is that meant to be done?
NSUnderlineStyle is an enum, which you cannot extend or override. You can of course provide a random raw Int value for the attribute, not covered by the enum cases:
self.addAttribute(NSUnderlineStyleAttributeName, value: 100022, range: lastUpdatedWordRange)
Which will deliver an "invalid" but usable underlineType to the Layout Manger:
But this hardly feels safe and is definitely inelegant.
I was not able to find any examples online or further clues in Apple documentation on what those mythical custom underline style types look like. I'd love to know if I'm missing something obvious.
I have an example project here that I used for a talk on TextKit that I gave a while back that does exactly what you're looking for: https://github.com/dtweston/text-kit-example
The underline in this case is a squiggly line:
The meat of the solution is a custom NSLayoutManager:
let CustomUnderlineStyle = 0x11
class UnderlineLayoutManager: NSLayoutManager {
func drawFancyUnderlineForRect(_ rect: CGRect) {
let left = rect.minX
let bottom = rect.maxY
let width = rect.width
let path = UIBezierPath()
path.move(to: CGPoint(x: left, y: bottom))
var x = left
var y = bottom
var i = 0
while (x <= left + width) {
path.addLine(to: CGPoint(x: x, y: y))
x += 2
if i % 2 == 0 {
y = bottom + 2.0
}
else {
y = bottom
}
i += 1;
}
path.stroke()
}
override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) {
if underlineVal.rawValue & CustomUnderlineStyle == CustomUnderlineStyle {
let charRange = characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
if let underlineColor = textStorage?.attribute(NSUnderlineColorAttributeName, at: charRange.location, effectiveRange: nil) as? UIColor {
underlineColor.setStroke()
}
if let container = textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) {
let boundingRect = self.boundingRect(forGlyphRange: glyphRange, in: container)
let offsetRect = boundingRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y)
drawFancyUnderlineForRect(offsetRect)
}
}
else {
super.drawUnderline(forGlyphRange: glyphRange, underlineType: underlineVal, baselineOffset: baselineOffset, lineFragmentRect: lineRect, lineFragmentGlyphRange: lineGlyphRange, containerOrigin: containerOrigin)
}
}
}

Swift How to calculate one line text height from its font

I ran into an issue where I needed to animate translating a label vertically the same distance of a textField's text height. In most cases just textField.bounds.heigt but if the textField's height is bigger than the text height it will not be any good for me. So I need to know:
How to calculate the line height of the string text from its UIFont?
Regarding the duplicate:
There's a little bit different of what I need. that answer(which I've referenced in my answer) get the total height depending on 1) the string 2) the width 3) the font. What I needed is one line height dpending only on the font.
UIFont has a property lineHeight:
if let font = _textView.font {
let height = font.lineHeight
}
where font is your font
I have been searching for a way to do that and find this answer where it has a String extension to calculate the size for the string and a given font. I have modified it to do what I want (get the line height of text written using a font.):
extension UIFont {
func calculateHeight(text: String, width: CGFloat) -> CGFloat {
let constraintRect = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSAttributedStringKey.font: self],
context: nil)
return boundingBox.height
}
}
I hope this helps someone looking for it. (may be myself in the future).
I've used this String extension in the past to draw some text as opposed to creating a UILabel somewhere. I don't like the fact that I can't seem to get the real height of the specific text I want to draw (not every string contains capital letters or characters with descenders, etc.) I've used a couple of enums for horizontal and vertical alignment around the given point. Open to ideas on the vertical height.
public func draw(at pt: CGPoint,
font: UIFont? = UIFont.systemFont(ofSize: 12),
color: UIColor? = .black,
align: HorizontalAlignment? = .Center,
vAlign: VerticalAlignment? = .Middle)
{
let attributes: [NSAttributedString.Key : Any] = [.font: font!,
.foregroundColor: color!]
let size = self.boundingRect(with: CGSize(width: 0, height: 0),
options: [ .usesFontLeading ],
attributes: [ .font: font! ],
context: nil).size
var x = pt.x
var y = pt.y
if align == .Center {
x -= (size.width / 2)
} else if align == .Right {
x -= size.width
}
if vAlign == .Middle {
y -= (size.height / 2)
} else if vAlign == .Bottom {
y -= size.height
}
let rect = CGRect(x: x, y: y, width: size.width, height: size.height)
draw(in: rect, withAttributes: attributes)
}

Resources