Change values of variables in class swift - class

I have 2 classed: Game and ViewController (subclass of NSViewController) that's Game class:
class Game {
required init(num : CGFloat, picName : String, gameName : String, description : String, windowHeight : CGFloat) {
self.number = num
self.pictureName = picName
self.name = gameName
self.desc = description
self.height = windowHeight
}
var number : CGFloat
var pictureName : String
var name : String
var desc : String
var height : CGFloat
var image : NSImage {
return NSImage(named: pictureName)!
}
var imageView : NSImageView {
return NSImageView(frame: NSRect(x: 0, y: height - (110 * (number - 1)) - 100, width: 100, height: 100))
}
var nameField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
}
var descField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 140, width: 300, height: 60))
}
func setImage() {
imageView.image = image
}
func setNameField() {
nameField.font = NSFont(name: "System", size: 15.0)
nameField.stringValue = "Name"
nameField.alignment = NSTextAlignment(rawValue: 2)!
nameField.selectable = false
nameField.editable = false
}
func setDescField() {
descField.selectable = false
descField.editable = false
descField.stringValue = desc
descField.font = NSFont(name: "System", size: 11.0)
}
}
And in viewDidAppear I do this:
var size = self.view.window?.frame.size
var newGame = Game(num: 1, picName: "Bomb.png", gameName: "Bomber", description: "Just a simpla application", windowHeight: size!.height)
newGame.setDescField()
newGame.setImage()
newGame.setNameField()
println(newGame.descField.editable)
self.view.addSubview(newGame.imageView)
self.view.addSubview(newGame.descField)
self.view.addSubview(newGame.nameField)
And descField.editable is always true, even though I write descField.editable = false in function in class Game. How to fix it?

I found the solution. You need to declare all fields and image view as lazy variables and work with them in init:
import Foundation
import Cocoa
import AppKit
class Game {
init(num : CGFloat, picName : String, gameName : String, description : String, windowHeight : CGFloat, sender : NSViewController) {
self.number = num
self.pictureName = picName
self.name = gameName
self.desc = description
self.height = windowHeight
imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
nameField = NSTextField(frame: NSRect(x: 110, y: 60, width: 300, height: 30))
descField = NSTextField(frame: NSRect(x: 110, y: 0, width: 300, height: 60))
nameField.font = NSFont(name: "System", size: 15.0)
nameField.stringValue = self.name
nameField.alignment = NSTextAlignment(rawValue: 2)!
nameField.selectable = false
nameField.editable = false
nameField.backgroundColor = sender.view.window?.backgroundColor
nameField.bordered = false
descField.selectable = false
descField.editable = false
descField.stringValue = desc
descField.font = NSFont(name: "System", size: 11.0)
descField.backgroundColor = NSColor.controlColor()
descField.bordered = true
descField.backgroundColor = sender.view.window?.backgroundColor
imageView.image = NSImage(named: self.pictureName)!
gameView = NSView(frame: NSRect(x: 0, y: /*height - (100 * num)*/ height - (120 * num), width: 600, height: 100))
button = NSButton(frame: NSRect(x: 0, y: 0, width: 600, height: 100))
button.tag = Int(number)
button.action = nil
var gesture = NSClickGestureRecognizer(target: sender, action: Selector("buttonPressed:"))
gesture.numberOfClicksRequired = 1
gesture.buttonMask = 0x1
button.addGestureRecognizer(gesture)
button.bezelStyle = NSBezelStyle(rawValue: 6)!
button.transparent = true
gameView.addSubview(nameField)
gameView.addSubview(imageView)
gameView.addSubview(descField)
gameView.addSubview(button)
}
var number : CGFloat
var pictureName : String
var name : String
var desc : String
var height : CGFloat
lazy var button : NSButton = NSButton()
lazy var imageView : NSImageView = NSImageView()
lazy var nameField : NSTextField = NSTextField()
lazy var descField : NSTextField = NSTextField()
lazy var gameView : NSView = NSView()
}
And then you call this all like that:
var newGame = Game(num: number, picName: "Bomb2.png", gameName: "Bomber", description: "Just a simple application", windowHeight: size!.height, sender: self)
self.view.addSubview(newGame.gameView)

The way your code is written, you're generating a new instance of NSTextField every time you access nameField, and so on.
You want to use lazy instantiation, which is easily supported by the adding the 'lazy' keyword:
lazy var nameField : NSTextField {
return NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
}
or even more simply:
lazy var nameField = NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 100, width: 300, height: 30))
Do the same for image, imageView, and descField.

How about like that:
var descField = NSTextField(frame: NSRect(x: 110, y: height - (110 * (number - 1)) - 140, width: 300, height: 60))
var editable: Bool {
get {
return descField.editable
}
set {
descField.editable = newValue
}
}
And the you can use your game instance:
var newGame //......
newGame.editable = true

Related

Create custom Line graph with shadow using CAShapeLayer swift

I have modified Line graph of Minh Nguyen to some extend to show two lines one for systolic and othere for diastolic.
The first image show the how the graph should look like and second image is what I have achieved.
struct PointEntry {
let systolic: Int
let diastolic: Int
let label: String
}
extension PointEntry: Comparable {
static func <(lhs: PointEntry, rhs: PointEntry) -> Bool {
return lhs.systolic < rhs.systolic || lhs.systolic < rhs.systolic
}
static func ==(lhs: PointEntry, rhs: PointEntry) -> Bool {
return lhs.systolic == rhs.systolic && lhs.diastolic == rhs.diastolic
}
}
class LineChart: UIView {
/// gap between each point
let lineGap: CGFloat = 30.0
/// preseved space at top of the chart
let topSpace: CGFloat = 20.0
/// preserved space at bottom of the chart to show labels along the Y axis
let bottomSpace: CGFloat = 40.0
/// The top most horizontal line in the chart will be 10% higher than the highest value in the chart
let topHorizontalLine: CGFloat = 110.0 / 100.0
/// Dot inner Radius
var innerRadius: CGFloat = 8
/// Dot outer Radius
var outerRadius: CGFloat = 12
var dataEntries: [PointEntry]? {
didSet {
self.setNeedsLayout()
}
}
/// Contains the main line which represents the data
private let dataLayer: CALayer = CALayer()
/// Contains dataLayer and gradientLayer
private let mainLayer: CALayer = CALayer()
/// Contains mainLayer and label for each data entry
private let scrollView: UIScrollView = {
let view = UIScrollView()
view.showsVerticalScrollIndicator = false
view.showsHorizontalScrollIndicator = false
return view
}()
/// Contains horizontal lines
private let gridLayer: CALayer = CALayer()
/// An array of CGPoint on dataLayer coordinate system that the main line will go through. These points will be calculated from dataEntries array
private var systolicDataPoint: [CGPoint]?
private var daistolicDataPoint: [CGPoint]?
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
convenience init() {
self.init(frame: CGRect.zero)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
private func setupView() {
mainLayer.addSublayer(dataLayer)
mainLayer.addSublayer(gridLayer)
scrollView.layer.addSublayer(mainLayer)
self.addSubview(scrollView)
}
override func layoutSubviews() {
scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height)
if let dataEntries = dataEntries {
scrollView.contentSize = CGSize(width: CGFloat(dataEntries.count) * lineGap + 30, height: self.frame.size.height)
mainLayer.frame = CGRect(x: 0, y: 0, width: CGFloat(dataEntries.count) * lineGap + 30, height: self.frame.size.height)
dataLayer.frame = CGRect(x: 0, y: topSpace, width: mainLayer.frame.width, height: mainLayer.frame.height - topSpace - bottomSpace)
systolicGradientLayer.frame = dataLayer.frame
diastolicGradientLayer.frame = dataLayer.frame
systolicDataPoint = convertDataEntriesToPoints(entries: dataEntries, isSystolic: true)
daistolicDataPoint = convertDataEntriesToPoints(entries: dataEntries, isSystolic: false)
gridLayer.frame = CGRect(x: 0, y: topSpace, width: CGFloat(dataEntries.count) * lineGap + 30, height: mainLayer.frame.height - topSpace - bottomSpace)
clean()
drawHorizontalLines()
drawVerticleLine()
drawChart(for: systolicDataPoint, color: .blue)
drawChart(for: daistolicDataPoint, color: .green)
drawLables()
}
}
/// Convert an array of PointEntry to an array of CGPoint on dataLayer coordinate system
/// - Parameter entries: Arrays of PointEntry
private func convertDataEntriesToPoints(entries: [PointEntry], isSystolic: Bool) -> [CGPoint] {
var result: [CGPoint] = []
// let gridValues: [CGFloat] = [0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1.0, 1.05]
for (index, value) in entries.enumerated() {
let difference: CGFloat = 0.125 / 30
let userValue: CGFloat = isSystolic ? CGFloat(value.systolic) : CGFloat(value.diastolic)
var height = (userValue - 30.0) * difference
height = (1.0 - height) * gridLayer.frame.size.height
let point = CGPoint(x: CGFloat(index)*lineGap + 40, y: height)
result.append(point)
}
return result
}
/// Draw a zigzag line connecting all points in dataPoints
private func drawChart(for points: [CGPoint]?, color: UIColor) {
if let dataPoints = points, dataPoints.count > 0 {
guard let path = createPath(for: points) else { return }
let lineLayer = CAShapeLayer()
lineLayer.path = path.cgPath
lineLayer.strokeColor = color.cgColor
lineLayer.fillColor = UIColor.clear.cgColor
dataLayer.addSublayer(lineLayer)
}
}
/// Create a zigzag bezier path that connects all points in dataPoints
private func createPath(for points: [CGPoint]?) -> UIBezierPath? {
guard let dataPoints = points, dataPoints.count > 0 else {
return nil
}
let path = UIBezierPath()
path.move(to: dataPoints[0])
for i in 1..<dataPoints.count {
path.addLine(to: dataPoints[i])
}
return path
}
/// Create titles at the bottom for all entries showed in the chart
private func drawLables() {
if let dataEntries = dataEntries,
dataEntries.count > 0 {
for i in 0..<dataEntries.count {
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: lineGap*CGFloat(i) - lineGap/2 + 40, y: mainLayer.frame.size.height - bottomSpace/2 - 8, width: lineGap, height: 16)
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.backgroundColor = UIColor.clear.cgColor
textLayer.alignmentMode = CATextLayerAlignmentMode.center
textLayer.contentsScale = UIScreen.main.scale
textLayer.font = CTFontCreateWithName(UIFont.systemFont(ofSize: 0).fontName as CFString, 0, nil)
textLayer.fontSize = 11
textLayer.string = dataEntries[i].label
mainLayer.addSublayer(textLayer)
}
}
}
/// Create horizontal lines (grid lines) and show the value of each line
private func drawHorizontalLines() {
let gridValues: [CGFloat] = [1.05, 1.0, 0.875, 0.75, 0.625, 0.5, 0.375, 0.25, 0.125]
let gridText = ["", "30", "60", "90", "120", "150", "180", "210", "240"]
for (index, value) in gridValues.enumerated() {
let height = value * gridLayer.frame.size.height
let path = UIBezierPath()
if value == gridValues.first! {
path.move(to: CGPoint(x: 30, y: height))
} else {
path.move(to: CGPoint(x: 28, y: height))
}
path.addLine(to: CGPoint(x: gridLayer.frame.size.width, y: height))
let lineLayer = CAShapeLayer()
lineLayer.path = path.cgPath
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.strokeColor = UIColor.black.cgColor
if value != gridValues.first! {
lineLayer.lineDashPattern = [4, 4]
}
lineLayer.lineWidth = 0.5
gridLayer.addSublayer(lineLayer)
let textLayer = CATextLayer()
textLayer.frame = CGRect(x: 4, y: height-8, width: 50, height: 16)
textLayer.foregroundColor = UIColor.black.cgColor
textLayer.backgroundColor = UIColor.clear.cgColor
textLayer.contentsScale = UIScreen.main.scale
textLayer.font = CTFontCreateWithName(UIFont.systemFont(ofSize: 0).fontName as CFString, 0, nil)
textLayer.fontSize = 12
textLayer.string = gridText[index]
gridLayer.addSublayer(textLayer)
}
}
private func drawVerticleLine() {
let height = gridLayer.frame.size.height * 1.05
let path = UIBezierPath()
path.move(to: CGPoint(x: 30, y: 0))
path.addLine(to: CGPoint(x: 30, y: height))
let lineLayer = CAShapeLayer()
lineLayer.path = path.cgPath
lineLayer.fillColor = UIColor.clear.cgColor
lineLayer.strokeColor = UIColor.black.cgColor
lineLayer.lineWidth = 0.5
gridLayer.addSublayer(lineLayer)
}
private func clean() {
mainLayer.sublayers?.forEach({
if $0 is CATextLayer {
$0.removeFromSuperlayer()
}
})
dataLayer.sublayers?.forEach({$0.removeFromSuperlayer()})
gridLayer.sublayers?.forEach({$0.removeFromSuperlayer()})
}
}
How can I add shadow to lines like shown in the first image and add simple line drawing animation to the Graph?

Multiple NSTextField in an NSAlert - MacOS

I am putting together a login dialog using an NSAlert in Swift 4 on MacOS. Here is the code I have so far:
func dialogOKCancel(question: String, text: String) -> (String, String) {
let alert = NSAlert()
alert.messageText = question
alert.informativeText = text
alert.alertStyle = .warning
alert.addButton(withTitle: "Login")
alert.addButton(withTitle: "Cancel")
let unameField = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
let passField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 24))
let stackViewer = NSStackView()
stackViewer.addSubview(unameField)
alert.accessoryView = stackViewer
let response: NSApplication.ModalResponse = alert.runModal()
if (response == NSApplication.ModalResponse.alertFirstButtonReturn) {
return (unameField.stringValue, passField.stringValue)
} else {
return ("", "")
}
}
This works just fine to show either the unameField or passField when I do alert.accessoryView = passField. But I want to show both fields in the same dialog. As you can see I've experimented with NSStackView (and others), but I have not found a solution to show both elements.
Hope will help you ...
You can use NSStackView and addSubview 2 item : unameField and passField.
But you must set frame for NSStackView and 2 item on NSStackView.
This is my code, you can refer:
let unameField = NSTextField(frame: NSRect(x: 0, y: 2, width: 200, height: 24))
let passField = NSSecureTextField(frame: NSRect(x: 0, y: 28, width: 200, height: 24))
let stackViewer = NSStackView(frame: NSRect(x: 0, y: 0, width: 200, height: 58))
stackViewer.addSubview(unameField)
stackViewer.addSubview(passField)
alert.accessoryView = stackViewer
And this is my result:
my result
My code: https://github.com/nhhthuanck/CustomizeNSAlertMacOS

Add view to my UIView used for draw(rect:)

I'm trying to make my own chart thing, so I opened my playground and started typing !
So I've learn how to use, draw(), so far so cool. But now, I'm trying to add some text on it. So here's my code :
import UIKit
class drawGraphMonthly: UIView {
var arrayOfDataPerMonth: [Int]?
var months: [String]?
var goldenRatioForData: CGFloat?
var goldenRationForMonth: CGFloat?
var monthStackView: UIStackView?
init(frame: CGRect, arrayOfDataPerMonth: [Int]) {
super.init(frame: frame)
self.arrayOfDataPerMonth = arrayOfDataPerMonth
self.goldenRatioForData = (frame.size.height / 2) / CGFloat((self.arrayOfDataPerMonth!.max())!)
self.goldenRationForMonth = frame.size.width / CGFloat((self.arrayOfDataPerMonth?.count)!)
self.months = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"]
self.monthStackView = {
let sv = UIStackView()
sv.translatesAutoresizingMaskIntoConstraints = false
sv.backgroundColor = .red
sv.distribution = .fillEqually
sv.axis = .horizontal
for month in self.months! {
let monthLabel: UILabel = {
let label = UILabel()
label.text = month
return label
}()
sv.addArrangedSubview(monthLabel)
}
return sv
}()
self.addSubview(monthStackView!)
monthStackView?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
monthStackView?.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
monthStackView?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
monthStackView?.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
// On dessine le background
let backgroundView = UIBezierPath(rect: CGRect(x: frame.size.width, y: frame.size.height, width: 1, height: 1))
let secondPoint = CGPoint(x: 0, y: frame.size.height)
let thirdPointData = getY(y: (arrayOfDataPerMonth?[(arrayOfDataPerMonth?.count)! - 1])!)
let thirdPoint = CGPoint(x: 0, y: thirdPointData)
backgroundView.addLine(to: secondPoint)
backgroundView.addLine(to: thirdPoint)
var countBG = 1
while countBG <= (arrayOfDataPerMonth?.count)! {
let x = CGFloat(countBG) * goldenRationForMonth!
let y = getY(y: (arrayOfDataPerMonth?[countBG - 1])!)
let point = CGPoint(x: x, y: y)
backgroundView.addLine(to: point)
countBG += 1
}
UIColor.white.setFill()
backgroundView.fill()
// On dessine le trait
let line = UIBezierPath(rect: CGRect(x: 0, y: thirdPointData, width: 0, height: 0))
var countLine = 1
while countLine <= (arrayOfDataPerMonth?.count)! {
let x = CGFloat(countLine) * goldenRationForMonth!
let y = getY(y: (arrayOfDataPerMonth?[countLine - 1])!)
let point = CGPoint(x: x, y: y)
line.addLine(to: point)
countLine += 1
}
UIColor.blue.setStroke()
line.lineWidth = 4
line.stroke()
}
func getY(y: Int) -> CGFloat {
var yToReturn : CGFloat = 0
yToReturn = (frame.size.height - (CGFloat(y) * self.goldenRatioForData!)) - 30
return yToReturn
}
}
let array = [21, 34, 36, 33, 31, 27, 23, 19, 8, 5, 25, 14]
let view = drawGraphMonthly(frame: CGRect(x: 0, y: 0, width: 414, height: 250), arrayOfDataPerMonth: array)
view.backgroundColor = UIColor(red:0.20, green:0.20, blue:0.20, alpha:1.0)
You should have the same result as I have if you copy/paste this into a Playground.
So my question is, why is my stackview not showing ?

How to make this UIButton's action operate correctly?

Every time I run this swift playground (on Xcode), I get an error on the last line saying:
'PlaygroundView' cannot be constructed because it has no accessible initialisers.
I also have another error on line 4 saying:
class 'PlaygroundView' has no initializers.
import UIKit
import PlaygroundSupport
class PlaygroundView:UIViewController {
var introImage:UIImageView
var introButton:UIButton
override func loadView() {
print("workspace started running.")
//Main Constant and Variable Declarations/General Setup
let mainView = UIView()
//Main View Modifications & styling
mainView.backgroundColor = UIColor.init(colorLiteralRed: 250, green: 250, blue: 250, alpha: 1)
func addItem(item: UIView) {
mainView.addSubview(item)
}
//Sub-Element Constant and Variable Declarations
introImage = UIImageView(frame: CGRect(x: 87.5, y: -300, width: 200, height: 200))
introImage.layer.borderColor = UIColor.black.cgColor
introImage.layer.borderWidth = 4
introImage.alpha = 0
introImage.transform = CGAffineTransform(rotationAngle: -90.0 * 3.14/180.0)
introImage.image = UIImage(named: "profile_pic.png")
introImage.image?.accessibilityFrame = introImage.frame
introImage.layer.cornerRadius = 100
addItem(item: introImage)
introButton = UIButton(frame: CGRect(x: 87.5, y: 900, width: 200, height: 30))
introButton.alpha = 0
introButton.setTitle("Tap to meet me", for: .normal)
introButton.titleLabel?.font = UIFont(name: "Avenir Book", size: 20)
introButton.backgroundColor = UIColor.black
introButton.layer.cornerRadius = 15
introButton.layer.borderWidth = 5
addItem(item: introButton)
introButton.addTarget(self, action: #selector(self.introButtonAction), for: .touchDown)
UIView.animate(withDuration: 1.5, delay: 1, options: .curveEaseInOut, animations: {
self.introImage.frame = CGRect(x: 87.5, y: 175, width: 200, height: 200)
self.introButton.frame = CGRect(x: 87.5, y: 400, width: 200, height: 30)
self.introImage.transform = CGAffineTransform(rotationAngle: 0.0 * 3.14/180.0)
self.introImage.alpha = 1
self.introButton.alpha = 1
}) { (mainAnimationComped) in
if mainAnimationComped == true {
print("introduction animation comped.")
}
}
}
//User Interface Actions
func introButtonAction() {
print("user interacted with user interface")
}
}
PlaygroundPage.current.liveView = PlaygroundView()
How would I fix this problem?
That's because in Swift, you need to initialize variables before use them in the code. On the other hand, since in this case you set them explicitly in your function, you might declare them as implicitly unwrapped optionals
var introImage:UIImageView!
var introButton:UIButton!
and this should work without changing anything else in your code
Add ? after introImage and introButton
var introImage:UIImageView?
var introButton:UIButton?

Custom View Broken When Window is Resized (Cocoa)

I am experiencing this weird bug with my custom view. The custom view is supposed to show meters of rating distribution. It gets added to a cell view of an outline view.
When I resize the window, the custom view somehow gets squished and looks broken. I have pasted the drawRect of the custom view below.
override func drawRect(r: NSRect) {
super.drawRect(r)
var goodRect: NSRect?
var okRect: NSRect?
var badRect: NSRect?
let barHeight = CGFloat(10.0)
if self.goodPercent != 0.0 {
goodRect = NSRect(x: 0, y: 0, width: r.width * CGFloat(goodPercent), height: barHeight)
let goodPath = NSBezierPath(roundedRect: goodRect!, xRadius: 6, yRadius: 6)
RatingDistributionView.goodColor.setFill()
goodPath.fill()
}
if self.okPercent != 0.0 {
let okX = CGFloat(goodRect?.width ?? 0.0)
okRect = NSRect(x: okX, y: 0, width: r.width * CGFloat(okPercent), height: barHeight)
let okPath = NSBezierPath(roundedRect: okRect!, xRadius: 6, yRadius: 6)
RatingDistributionView.okColor.setFill()
okPath.fill()
}
if self.badPercent != 0.0 {
var badX: CGFloat
//Cases:
//Good persent and OK present - badX = okRect.x + okRect.width
//Good persent and OK missing - badX = goodRect.x + goodRect.width
//Good missing and OK present - badX = okRect.x + okRect.width
//Both missing -
if okRect != nil {
badX = okRect!.origin.x + okRect!.width
}else if goodRect != nil {
badX = goodRect!.origin.x + goodRect!.width
} else {
badX = 0.0
}
badRect = NSRect(x: badX, y: 0, width: r.width * CGFloat(badPercent), height: barHeight)
let badPath = NSBezierPath(roundedRect: badRect!, xRadius: 6, yRadius: 6)
RatingDistributionView.badColor.setFill()
badPath.fill()
}
//Draw dividers
let divWidth = CGFloat(6.75)
if self.goodPercent != 0.0 && (self.okPercent != 0.0 || self.badPercent != 0.0) {
let divX = goodRect!.origin.x + goodRect!.width
let divRect = NSRect(x: divX - (divWidth / 2.0), y: 0.0, width: divWidth, height: barHeight)
let divPath = NSBezierPath(roundedRect: divRect, xRadius: 0, yRadius: 0)
NSColor.whiteColor().setFill()
divPath.fill()
}
if self.okPercent != 0.0 && self.badPercent != 0.0 {
let divX = okRect!.origin.x + okRect!.width
let divRect = NSRect(x: divX - (divWidth / 2.0), y: 0.0, width: divWidth, height: barHeight)
let divPath = NSBezierPath(roundedRect: divRect, xRadius: 0, yRadius: 0)
NSColor.whiteColor().setFill()
divPath.fill()
}
}
AN alternative solution for your problem is to use NSView. You can have a container view with rounded corner and then drawing subviews (red, orange, green) in that container. like this;
I have written a class for it that you may customise according to your requirements;
public class CProgressView:NSView {
private lazy var goodView:NSView = {
let viw:NSView = NSView(frame: NSRect.zero);
viw.layer = CALayer();
viw.layer?.backgroundColor = NSColor.greenColor().CGColor;
self.addSubview(viw)
return viw;
} ();
private lazy var okView:NSView = {
let viw:NSView = NSView(frame: NSRect.zero);
viw.layer = CALayer();
viw.layer?.backgroundColor = NSColor.orangeColor().CGColor;
self.addSubview(viw)
return viw;
} ();
private lazy var badView:NSView = {
let viw:NSView = NSView(frame: NSRect.zero);
viw.layer = CALayer();
viw.layer?.backgroundColor = NSColor.redColor().CGColor;
self.addSubview(viw)
return viw;
} ();
private var _goodProgress:CGFloat = 33;
private var _okProgress:CGFloat = 33;
private var _badProgress:CGFloat = 34;
private var goodViewFrame:NSRect {
get {
let rect:NSRect = NSRect(x: 0, y: 0, width: (self.frame.size.width * (_goodProgress / 100.0)), height: self.frame.size.height);
return rect;
}
}
private var okViewFrame:NSRect {
get {
let rect:NSRect = NSRect(x: self.goodViewFrame.size.width, y: 0, width: (self.frame.size.width * (_okProgress / 100.0)), height: self.frame.size.height);
return rect;
}
}
private var badViewFrame:NSRect {
get {
let width:CGFloat = (self.frame.size.width * (_badProgress / 100.0));
let rect:NSRect = NSRect(x: self.frame.size.width - width, y: 0, width: width, height: self.frame.size.height);
return rect;
}
}
override public init(frame frameRect: NSRect) {
super.init(frame: frameRect);
//--
self.commonInit();
}
required public init?(coder: NSCoder) {
super.init(coder: coder);
}
override public func awakeFromNib() {
super.awakeFromNib();
//--
self.commonInit();
}
private func commonInit() {
self.layer = CALayer();
self.layer!.cornerRadius = 15;
self.layer!.masksToBounds = true
//-
self.updateFrames();
}
public func updateProgress(goodProgressV:Int, okProgressV:Int, badProgressV:Int) {
guard ((goodProgressV + okProgressV + badProgressV) == 100) else {
NSLog("Total should be 100%");
return;
}
_goodProgress = CGFloat(goodProgressV);
_okProgress = CGFloat(okProgressV);
_badProgress = CGFloat(badProgressV);
//--
self.updateFrames();
}
private func updateFrames() {
self.layer?.backgroundColor = NSColor.grayColor().CGColor;
self.goodView.frame = self.goodViewFrame;
self.okView.frame = self.okViewFrame;
self.badView.frame = self.badViewFrame;
}
public override func resizeSubviewsWithOldSize(oldSize: NSSize) {
super.resizeSubviewsWithOldSize(oldSize);
//--
self.updateFrames();
}
}
Note: Call updateProgress() method for changing progress default is 33, 33 & 34 (33+33+34 = 100);
You may also download a sample project from the link below;
http://www.filedropper.com/osxtest
From the documentation of drawRect(_ dirtyRect: NSRect):
dirtyRect:
A rectangle defining the portion of the view that requires redrawing. This rectangle usually represents the portion of the view that requires updating. When responsive scrolling is enabled, this rectangle can also represent a nonvisible portion of the view that AppKit wants to cache.
Don't use dirtyRect for your calculations, use self.bounds.