I use IOS-charts for drawing of downloading speed. I give to chart function array of double which has byte values. It build chart. But I want to show in chart labels value in megabytes, kilobytes or byte. I can transform byte in these value with help NSByteFormatter. But there is one problem, a function that builds charts takes a single array of values (such as an array of bytes), and outputs the same values in the labels on the chart, I tried to find a solution to do this, but I found none. How can i do this?
Function which draw a chart
func setSendChart(description: [String], value: [Double]) {
var chartDataEntryArray = [ChartDataEntry]()
for item in 0..<description.count {
let chartEntry = ChartDataEntry(value: value[item], xIndex: item)
chartDataEntryArray.append(chartEntry)
}
let dataSet = LineChartDataSet(yVals: chartDataEntryArray, label: "")
dataSet.drawCircleHoleEnabled = false
dataSet.drawCirclesEnabled = false
dataSet.drawCubicEnabled = true
dataSet.drawFilledEnabled = true
dataSet.drawValuesEnabled = false
dataSet.fillColor = UIColor(red: 47/255, green: 206/255, blue: 255/255, alpha: 1.0)
let chartData = LineChartData(xVals: description, dataSet: dataSet)
sendChartView.data = chartData
}
You can't change the value the chart is using to draw; however, you could provide your own NSNumberFormatter implementation to get the formatted value you want. yAxis has valueFormatter, so does dataSet
Another way is, you subclass dataSet to pass your value text array, and override renderAxisLabels or drawValues to draw the text you want.
I would prefer the first one since it does not touch the library code
Related
I'm trying to implement pagination using NSLayoutManager and multiple text containers. Creating NSTextView/UITextView instances works as expected, and the text flows from text view to another.
However, I'd like to force a page break, ie. determine myself where to break onto the next container. I'm working with parsed content, so I can't insert any additional ASCII control characters into the text.
I tried subclassing NSLayoutManager and overriding textContainer(glyphIndex:, effectiveRange:), and here's a very brute-force example:
override func textContainer(forGlyphAt glyphIndex: Int, effectiveRange effectiveGlyphRange: NSRangePointer?) -> NSTextContainer? {
if glyphIndex > 100 {
return self.textContainers[1]
} else {
return super.textContainer(forGlyphAt: glyphIndex, effectiveRange: effectiveGlyphRange)
}
}
I'd expect this to move any glyphs after 100th index onto the second container, but the results are weird:
I suppose I'd have to subclass NSTextContainer and tell the layout manager that it's already full of text. It has a method called lineFragmentRect(forProposedRect:at:writingDirection:remaining:) but the documentation is very sparse, and I can't find any working examples.
Existing documentation around displaying text is very outdated, so any ideas or hints are welcome. I'm very confused about this, but still hopeful there is a simple way of telling the layout manager where to cut off content in each container.
One possible solution
NSTextContainer.exclusionPaths could be used to rule out the rest of the possible space in containers.
let glyphIndex = layoutManager.glyphIndexForCharacter(at: 100)
var rect = layoutManager.lineFragmentRect(forGlyphAt: glyphIndex, effectiveRange: nil)
// Get the line range and used rect
var lineRange = NSMakeRange(NSNotFound, 0)
var usedRect = layoutManager.lineFragmentUsedRect(forGlyphAt: gi, effectiveRange: NSRangePointer(&lineRange))
// Calculate the remainder of the line
let remainder = NSRange(location: glyphIndex, length: NSMaxRange(lineRange) - glyphIndex)
var rectCount:Int = 0
var breakRects = layoutManager.rectArray(forGlyphRange: remainder, withinSelectedGlyphRange: remainder, in: textContainer1, rectCount: UnsafeMutablePointer(&rectCount))
// Create the rect for the remainder of the line
var lineRect = breakRect!.pointee
lineRect.size.width = textContainer1.size.width - lineRect.origin.x
// Then create a rect to cover up the rest
var coverRest = NSMakeRect(0, lineRect.origin.y + lineRect.height, textContainer1.size.width, CGFloat.greatestFiniteMagnitude)
// Add exclusion paths
textContainer1.exclusionPaths = [NSBezierPath(rect: lineRect), NSBezierPath(rect: coverRest)]
This results in expected behavior:
This requires a ton of calculations, and text containers with exclusion paths are noticeably slower. The app could potentially have hundreds of text views, which makes this quite inefficient.
I am working on an iOS app (just a personal project at the moment) and I am trying to change a label bg and text colours based on variables stored in an array, and I'm really struggling!
I have tried using a few methods, but I am very new to Swift and don't understand all of the logic behind what is and isn't best practice here.
This is my array:
let testItem = [["Name Here"], ["red: 1.0, green: 0, blue: 0, alpha: 1"], ["red: 1.0, green: 0, blue: 0, alpha: 1"]]
And basically I thought I would be able to do something like this:
labelOne.backgroundColor = UIColor(testItem[1])
labelOne.textColor = UIColor(testItem[2])
But of course, that won't work... so any ideas?
There are a number of ways you can do this, and here are a few to help you understand data collections (there are other ways).
You can simply create an array of colors [UIColor]:
let colors = [UIColor.red, UIColor.green, UIColor.blue]
labelOne.backgroundColor = colors[0]
labelOne.textColor = colors[2]
You can store all of that one label's styling in a dictionary [String : UIColor]:
let labelOneStyling = ["background": UIColor.red, "text": UIColor.blue]
labelOne.backgroundColor = labelOneStyling["background"]
labelOne.textColor = labelOneStyling["text"]
You can store all label styling in a dictionary of dictionaries [String : [String : UIColor]]:
let allLabelStyling = ["labelOne": ["background": UIColor.red, "text": UIColor.blue], "labelTwo": ["background": UIColor.green, "text": UIColor.yellow]]
labelOne.backgroundColor = allLabelStyling["labelOne"]?["background"]
labelOne.textColor = allLabelStyling["labelOne"]?["text"]
A really useful tip is to option-click on any property, like the colors array in the first example, and select "Show Quick Help". Xcode will tell you what type that property is. You will see let colors: [UIColor] which tells you that colors is a constant of type array of UIColors. Swift is a strongly-typed language and types are the building blocks of OOP so if there is anything to learn right off the bat, types are it.
I am using the Swift Charts library by Daniel Gindi. I am trying to show individual values for each point in a scatter plot.
I have set drawValuesEnabled to true for the set and created a custom IValueFormatter. However, it appears that the method in the formatter class is never even being called, and no labels are shown. What else could be the cause of this?
EDIT: Code to create scatter plot is as follows:
let graphView: ScatterChartView = {
let scatter = ScatterChartView()
scatter.translatesAutoresizingMaskIntoConstraints = false
scatter.chartDescription?.enabled = true
scatter.dragEnabled = true
scatter.setScaleEnabled(true)
//scatter.maxVisibleCount = 200
scatter.pinchZoomEnabled = false
scatter.backgroundColor = .clear
scatter.leftAxis.enabled = false
scatter.rightAxis.enabled = true
scatter.rightAxis.drawGridLinesEnabled = false
scatter.rightAxis.valueFormatter = TimeValueFormatter()
scatter.xAxis.labelPosition = XAxis.LabelPosition.bottom
scatter.xAxis.axisMaxLabels = 8
scatter.xAxis.granularity = 1
scatter.xAxis.drawLabelsEnabled = true
scatter.xAxis.drawGridLinesEnabled = false
//date formatter should be provided by controller
scatter.legend.enabled = false
return scatter
}()
And the data set (with x-axis as integers and y-axis as time):
self.dataSet = {
let set = ScatterChartDataSet(values: entries)
set.setScatterShape(.circle)
set.setColor(UIColor.PRColors.prGreen)
set.valueFormatter = TimeValueFormatter()
return set
}()
Expected result is labels with the y-value shown by each point, as in the example project.
Figured it out. Turns out, when you create a data set using an array of ChartDataEntry objects, the array must be immutable (let rather than var), or labels will not be shown.
In a NSAttributed type statement, I want to keep the existing attributed value and give it a new attributed value.
The problem is that replacingOccurrences is only possible for string types, as I want to give a new value every time the word appears in the entire sentence.
If I change NSAttributedString to string type, the attributed value is deleted. I must keep the existing values.
How can I do that?
To get this working,
1. First you need to find the indices of all the duplicate substrings existing in a string. To get that you can use this: https://stackoverflow.com/a/40413665/5716829
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
2. Next you need to apply your desired attribute to substring at each index, i.e.
let str = "The problem is that replacingOccurrences Hello is only possible for string types, as I want to give Hello a new value every time Hello the word appears in the entire sentence Hello."
let indices = str.indicesOf(string: "Hello")
let attrStr = NSMutableAttributedString(string: str, attributes: [.foregroundColor : UIColor.blue])
for index in indices
{
//You can write your own logic to specify the color for each duplicate. I have used some hardcode indices
var color: UIColor
switch index
{
case 41:
color = .orange
case 100:
color = .magenta
case 129:
color = .green
default:
color = .red
}
attrStr.addAttribute(.foregroundColor, value: color, range: NSRange(location: index, length: "Hello".count))
}
Screenshot:
Let me know if you still face any issues. Happy Coding..🙂
You can use replaceCharacters. You can find the range of the substring you want to remove and use it as your range.
I am looking for a way to stop an NSTextView from falling back on a cascading font when certain characters in the display string are unavailable in the specified font.
I am writing an app in which it is important that the user know if certain characters (those in East Asian character sets, for example) are available in a given font. If these characters are not available, I want the field to display these as boxes, blank spaces, or something similar. However, the default is for the textview to fall back on a cascading font in which the characters are supported. Is there any way to change this behavior?
If this is not possible, might there be some way to detect which languages are supported by a given font?
Any help would be greatly appreciated.
I was able to make it work by creating extensions partially based on this answer.
The solution I ended up using was to iterate through each character in the string in question and check to see if it is contained in the font's character set. If it is not, the appearance is changed (in this case, changing the color and adding strikethrough) before it is added to an attributed string containing all of characters of the original string.
This is what it ended up looking like:
extension UnicodeScalar {
func isIn(font:NSFont)-> Bool {
let coreFont:CTFont = font
let charSet:CharacterSet = CTFontCopyCharacterSet(coreFont) as CharacterSet
if charSet.contains(self) {
return true
}
return false
}
}
extension NSFont {
func supportString(_ str:String)-> NSAttributedString {
let attString = NSMutableAttributedString(string: "")
for scalar in str.unicodeScalars {
if !scalar.isIn(font: self) {
let r:CGFloat = 200
let g:CGFloat = 0
let b:CGFloat = 0
let a:CGFloat = 0.2
let color = NSColor(red: r, green: g, blue: b, alpha: a)
let attcha = NSMutableAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:color, NSAttributedStringKey.strikethroughStyle:NSUnderlineStyle.styleSingle.rawValue])
attString.append(attcha)
} else {
let attcha = NSAttributedString(string: String(scalar), attributes: [NSAttributedStringKey.foregroundColor:NSColor.black])
attString.append(attcha)
}
}
return attString
}
}