How can I create a stacked progress indicator to use with the interface builder like this one from the storage management?
Is there any similar cocoa control?
If there isn't, should I try with creating a custom view, in witch I add a new view inside every time i call a method like progress.add(width: 10.0, color: NSColor(...))?
I just made a function to draw some views, you can play with it a bit.
func drawStackedProgress(percentages:[Float], width:Float, height:Float, x:Float, y:Float){
var currentX = x
// I just threw a bunch of random (mostly probably ugly) colors in this array. Go ahead and make sure there's as many colors as stacked elements.
var colors = [UIColor.red, UIColor.blue, UIColor.green, UIColor.brown, UIColor.cyan]
var index = -1
for percentage in percentages{
index += 1
let DynamicView=UIView(frame: CGRect(x: CGFloat(currentX), y: CGFloat(y), width: CGFloat(Double(percentage)*Double(width)), height: CGFloat(height)))
currentX+=Float(Double(percentages[index])*Double(width))
DynamicView.backgroundColor=colors[index]
self.view.addSubview(DynamicView)
}
}
Implementation:
drawStackedProgress(percentages: [0.1,0.3,0.5,0.1], width: 200, height: 20, x: 200, y: 200)
Update: Translated code for a mac application.
Corrected index out of range for colors.
public func drawStackedProgress(percentages: [CGFloat], width: CGFloat, height: CGFloat, x: CGFloat, y: CGFloat){
var currentX = x
var colors = [NSColor.red, NSColor.blue, NSColor.green, NSColor.brown, NSColor.cyan]
var index = -1
for percentage in percentages{
index += 1
let DynamicView = NSView(frame: CGRect(x: currentX, y: y, width: percentage * width, height: height))
currentX += percentages[index] * width
// if the colors have been all used, it starts choosing again from the first
DynamicView.layer?.backgroundColor = colors[index % colors.count].cgColor
self.addSubview(DynamicView)
}
}
I've made this simple pod, you can check it: https://github.com/adriatikgashi/MultipleProgressBar
private lazy var multipleProgressView: MultiProgressView = {
let view = MultiProgressView()
return view
}()
private var kUsageModels = [
UsagesModel(color: .red, value: 110.58),
UsagesModel(color: .green, value: 5.23),
UsagesModel(color: .blue, value: 1.25),
UsagesModel(color: .yellow, value: 0.58),
UsagesModel(color: .purple, value: 0.31),
UsagesModel(color: .lightGray, value: 32.51),
]
// Update the multiprogressBar
multipleProgressView.updateViews(usageModels: kUsageModels)
Related
I'm trying to draw labels above lines using a Canvas in SwiftUI. The lines draw at the correction position, however the labels are never drawn above the lines (see screenshot which shows both the "1" and "2" label drawn atop each other).
I use a for loop to get the position data from an array:
Canvas {context, size in
let font = Font.custom("Arial Rounded MT Bold", size: 16)
for line in TLDataModel.lines {
let resolved = context.resolve(Text(line.tickLabel).font(font))
let midPoint = CGPoint(x: line.points.startIndex, y: line.points.endIndex)
context.draw(resolved, at: midPoint, anchor: .center)
print("Line Label: \(line.tickLabel)")
print("..for points: \(line.points)")
var path = Path()
path.addLines(line.points)
context.stroke(path, with: .color(.blue), lineWidth: 1.0)
}
}.frame(width: (400), height: 100, alignment: .leading)
And my class:
class testDataModel: ObservableObject {
#Published var lines: [line] = []
init(){
var nl = line(points: [CGPoint(x: 20.0, y: 0.0), CGPoint(x: 20.0, y: 100.0)], tickLabel: "1")
addLines(newLine: nl)
nl = line(points: [CGPoint(x: 50.0, y: 0.0), CGPoint(x: 50, y: 100.0)], tickLabel: "2")
addLines(newLine: nl)
}
func addLines (newLine: line){
lines.append(newLine)
}
}
public struct line: Identifiable {
public let id = UUID()
let points: [CGPoint]
let tickLabel: String
}
I'm trying to write a most basic function that will set the parameters of a label.
First I set the position where I want to put a label:
var xCircularPos = UIScrollView.frame.width / 2
var yCircularPos = UIScrollView.frame.height * 0.15
And than write a function:
func textParameters(labelName: UILabel, text: String, xPosition: CGFloat, yPosition: CGFloat) {
labelName.textAlignment = .center
labelName.text = text
labelName.sizeToFit()
labelName.layer.position.x = CGFloat(0)
labelName.layer.position.y = CGFloat(0)
UIScrollView.addSubview(labelName)
}
Than I create a label and use a function:
let scoreLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 0, height: 21))
textParameters(labelName: scoreLabel, text: "Счет", xPosition: xCIrcularPos, yPosition: yCIrcularPos)
But nothing happens. When I set the same parameters for the label without a function - everything works fine. In both cases everything happens in viewDidLoad.
Can you please give a tip what I am missing?
I have two views. The parent view holds a CameraFeed UIViewControllerRepresentable which passes back the bounds of a AVMetadataFaceObject. I'm trying to draw overlays where that bounds should be. I'm getting kind of close, but my mappings aren't quite right. The CameraFeed passes back a scalar-style set of bounds and I'm multiplying my geometry reader by it.
The X and the Y seem to be swapped? (orange square works wrong and blue square works better)
The location doesn't QUITE line up perfectly.
Acknowledgements: Thanks to the HackingWithSwift InstaFilter and Hot Prospects examples, btw. They were very helpful to getting this far.
I do understand that there is a CALayer that I could draw on in the AV object, and if I went that way I could use the more powerful Vision framework but I was seeing if I could try this approach first. I also am aware that there is a CIFaceFeature I could be using too. Also, I don't have a TrueDepth front-facing camera to work with. I just wanted to see if I could hijack this seemingly simplest of solutions to make it work.
What am I missing about the how how the bounds of AVMetaDataObject work and approaches to doing the frame of reference transformation? Thanks in advance. Full Code on GitHub
struct CameraFeedView: View {
#State var foundFace:CGRect?
#State var geometryRect:CGRect = CGRect(x: 0, y: 0, width: 0, height: 0)
//#State var foundFaceAdjusted:CGRect?
//var testRect:CGRect = CGRect(
// x: 0.44112000039964916,
// y: 0.1979580322805941,
// width: 0.3337599992007017,
// height: 0.5941303606941507)
var body: some View {
GeometryReader(content: { geometry in
ZStack {
CameraFeed(codeTypes: [.face], completion: handleCameraReturn)
if (foundFace != nil) {
Rectangle()
.stroke()
.foregroundColor(.orange)
.frame(width: 100, height: 100, alignment: .topLeading)
.position(
x: geometry.size.width * foundFace!.origin.x,
y: geometry.size.height * foundFace!.origin.y)
FoundObject(frameRect: geometryRect, boundsRect: foundFace!)
.stroke()
.foregroundColor(.blue)
}
}
.onAppear(perform: {
let frame = geometry.frame(in: .global)
geometryRect = CGRect(
origin: CGPoint(x: frame.minX, y: frame.minY),
size: geometry.size
)
})
})
}
func handleCameraReturn(result: Result<CGRect, CameraFeed.CameraError>) {
switch result {
case .success(let bounds):
print(bounds)
foundFace = bounds
//TODO: Add a timer
case .failure(let error):
print("Scanning failed: \(error)")
foundFace = nil
}
}
}
struct FoundObject: Shape {
func reMapBoundries(frameRect:CGRect, boundsRect:CGRect) -> CGRect {
//Y bounded to width? Really?
let newY = (frameRect.width * boundsRect.origin.x) + (1.0-frameRect.origin.x)
//X bounded to height? Really?
let newX = (frameRect.height * boundsRect.origin.y) + (1.0-frameRect.origin.y)
let newWidth = 100//(frameRect.width * boundsRect.width)
let newHeight = 100//(frameRect.height * boundsRect.height)
let newRect = CGRect(
origin: CGPoint(x: newX, y: newY),
size: CGSize(width: newWidth, height: newHeight))
return newRect
}
let frameRect:CGRect
let boundsRect:CGRect
func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(reMapBoundries(frameRect: frameRect, boundsRect: boundsRect))
return path
}
}
I want a NSTextField with text direction as
how could I achieve this? thanks in advance for any help.
Choose orientation to Vertical and then set text.
The input must be look like that.
I missed the NSTextFieldpoints so did it with NSTextView thanks to comment of #Willeke.
Cocoa framework has some points to rotate UI components.
For your NSTextField
myTextField.rotate(byDegrees: 90)
set your width & height carefully.
I rotate the NSTextField
nameTF.rotate(byDegrees: 270)
calculate the new height and width of NSTextField
let newHeight = nameTF.bestHeight(for: nameTF.stringValue, width: nameTF.frame.width)
let newWidth = nameTF.bestWidth(for: nameTF.stringValue, height: nameTF.frame.height)
set the new frame of NSTextField
nameTF.frame = CGRect(x: nameTF.frame.origin.x, y: self.view.frame.height - newWidth - 30, width: newHeight, height: newWidth)
used the extension of NSTextField
extension NSTextField {
func bestHeight(for text: String, width: CGFloat) -> CGFloat {
stringValue = text
let height = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude)).height
return height
}
func bestWidth(for text: String, height: CGFloat) -> CGFloat {
stringValue = text
let width = cell!.cellSize(forBounds: NSRect(x: 0, y: 0, width: .greatestFiniteMagnitude, height: height)).width
return width
}
}
I know I can fill a rect using NSRectFill(bounds). However I wanted to preserve transparency for PDF output and I discovered that I can do that only with NSBezierPath(rect: bounds).fill()
What is the difference (behind the scenes) of those two?
func drawBackground() {
CGContextSaveGState(currentContext)
if (NSGraphicsContext.currentContextDrawingToScreen()) {
NSColor(patternImage: checkerboardImage).set()
NSRectFillUsingOperation(bounds, NSCompositingOperation.CompositeSourceOver)
}
NSColor.clearColor().setFill()
//NSRectFill(bounds) //option 1
NSBezierPath(rect: bounds).fill() // option 2
CGContextRestoreGState(currentContext)
}
extension NSImage {
static func checkerboardImageWithSize(size : CGFloat) -> NSImage {
let fullRect = NSRect(x: 0, y: 0, width: size, height: size)
let halfSize : CGFloat = size * 0.5;
let upperSquareRect = NSRect(x: 0, y: 0, width: halfSize, height: halfSize);
let bottomSquareRect = NSRect(x: halfSize, y: halfSize, width:halfSize, height: halfSize);
let image = NSImage(size: NSSize(width: size, height: size))
image.lockFocus()
NSColor.whiteColor()
NSRectFill(fullRect)
NSColor(deviceWhite: 0.0, alpha:0.1).set()
NSRectFill(upperSquareRect)
NSRectFill(bottomSquareRect)
image.unlockFocus()
return image
}
}
I'm mostly an iOS programmer and not very fluent these days over on the AppKit side of things, but my guess is that you're getting the wrong NSCompositingOperation. I see from the docs that NSRectFill uses NSCompositeCopy. Perhaps it would work better if you used NSRectFillUsingOperation, where you get to specify the compositing operation.