Swift, Cocoa - how to update view properties when using an NSGraphicsContext? - swift

If I call self.layer? in a draw() method to control the content of an NSView, the view's properties can be updated by the view controller. However, if I create a graphics context for drawing into, this relationship breaks and the view controller no longer updates the view's properties. Why?
In the simple example I provide, if you comment out the guard statement in TestView and call drawBackgroundWithoutContext(), the view is given a background colour when viewDidLoad() is called, and the colour is updated when the mouseDown event occurs.
As soon as a context is declared this breaks, even if you are still calling drawBackgroundWithoutContext().
import Cocoa
class ViewController: NSViewController {
var newR: CGFloat?
var newG: CGFloat?
var newB: CGFloat?
var tview = TestView(frame: NSRect(x: 20, y: 30, width: 100, height: 100))
override func viewDidLoad() {
super.viewDidLoad()
tview.r = 1.0
tview.g = 0.5
tview.b = 0.8
self.view.addSubview(tview)
}
override func mouseDown(with event: NSEvent) {
newR = 0.1
newG = 0.9
newB = 0.0
tview.r = newR
tview.g = newG
tview.b = newB
tview.draw(NSRect(x: 20, y: 30, width: 100, height: 100))
// tview.setNeedsDisplay(NSRect(x: 20, y: 30, width: 100, height: 100))
}
}
class TestView: NSView {
var r: CGFloat?
var g: CGFloat?
var b: CGFloat?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
// drawBackgroundWithoutContext(red: r, green: g, blue: b)
drawBackgroundWithContext(context: context, red: r, green: g, blue: b)
}
func drawBackgroundWithoutContext(red: CGFloat?, green: CGFloat?, blue: CGFloat?) {
self.layer?.backgroundColor = CGColor(red: red ?? 0.0, green: green ?? 0.0, blue: blue ?? 0.0, alpha: 1.0)
}
func drawBackgroundWithContext(context: CGContext, red: CGFloat?, green: CGFloat?, blue: CGFloat?) {
context.setFillColor(CGColor(red: red ?? 0.0, green: green ?? 0.0, blue: blue ?? 0.0, alpha: 1.0))
context.fill(CGRect(x: 20, y: 30, width: 100, height: 100))
}
}
I read that you should not actually call .draw() in the view controller and the correct method is setNeedsDisplay(), however this had no affect. I have left this call as a comment in the code so you can see what I tried.

Related

How to add custom text on ScreenSaverView in Swift?

Trying to make .saver in swift for MacOS, But don't know how to add custom text or Label on ScreenSaverView class which is the subclass from NSView.
Below is my piece of code, but doesn't work.
private var text: CATextLayer!
override func draw(_ rect: NSRect) {
// Draw a single frame in this function
self.layer?.backgroundColor = .init(red: 142/255, green: 167/255, blue: 125/255, alpha: 1)
text.removeFromSuperlayer()
text.string = "Test"
text.frame = CGRect(x: 100, y: 100, width: 200, height: 100)
text.foregroundColor = .white
text.backgroundColor = .clear
self.layer?.addSublayer(text)
}

Why are UIViews background color is not updating?

Switch statement works but won't reset view background colours etc.
I have a UIImage (icon) and a UIButton embedded within a UIView (of custom type DropShadowCircleView) as per image below.
When the walking button is tapped a var navigationOption is set to either walking or driving and setupNavigationSelectionView() is executed.
Problem is: case "walking" of the switch works perfectly, but case "driving" doesn't reset the UIView and icon tint color witcback to their original setting eg; background color etc.. any ideas why?
func setupNavigationSelectionView(){
switch navigationOption {
case "walking":
walkingBg.setGradientBackground(colourOne: softGreen, ColourTwo: softBlue)
walkingBg.layer.cornerRadius = walkingBg.frame.width / 2
walkingBg.clipsToBounds = true
walkingIcon.tintColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
case "driving":
walkingBg.backgroundColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
walkingBg.layer.cornerRadius = walkingBg.frame.width / 2
walkingBg.clipsToBounds = true
walkingIcon.tintColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
default:
break
}
}
EDIT: this is my DropShadowCircleView class
class DropShadowCircleView: UIView {
override func awakeFromNib() {
setupView()
super.awakeFromNib()
}
func setupView(){
self.layer.shadowOpacity = 0.50
self.layer.shadowRadius = 20
self.layer.shadowColor = UIColor.black.cgColor
self.layer.cornerRadius = self.frame.width / 2
}
}
EDIT: This is my setGradientBackground function which is within an extension file to UIView
func setGradientBackground(colourOne: UIColor, ColourTwo: UIColor) {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = [colourOne.cgColor, ColourTwo.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 1.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.0)
layer.insertSublayer(gradientLayer, at: 0)
}
You need to remove your gradient layer when you reset your icon.
Add this to your extension UIView:
func removeGradientBackground() {
guard
let idx = layer.sublayers?.index(where: { $0 is CAGradientLayer })
else { return }
layer.sublayers?.remove(at: idx)
}
and call it when you are resetting your icon.

Delete a filled NSBezierPath

I've drawn a red circle. Then I would like to delete it. How can I do that?
class Red: NSView {
var red = 255
var green = 0
var blue = 0
override func draw(_ dirtyRect: NSRect) {
let circleFillColor = NSColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: 1)
let cPath: NSBezierPath = NSBezierPath(ovalIn: dirtyRect)
circleFillColor.set()
cPath.fill()
}
}
override func viewDidLoad() {
super.viewDidLoad()
let signal = Red(frame: NSRect(x: 146, y: 18, width: 25, height: 25))
self.view.addSubview(signal)
}
You can use removeFromSuperview method of the view to get if removed, as such:
class ViewController: NSViewController {
var signal: Red?
override func viewDidLoad() {
super.viewDidLoad()
self.signal = Red(frame: NSRect(x: 146, y: 18, width: 25, height: 25))
self.view.addSubview(signal!)
}
func deleteCircle() {
self.signal?.removeFromSuperview()
self.signal = nil
}
}

CGColor and UIDeviceRGBColor leaks

I have a weird problem - the Xcode issue navigator reports that my app leaks CGColor and UIDeviceRGBColor.
The class in which this occurs seems fairly simple to me, but hoping someone can point me in the right direction.
import Foundation
internal enum TriangleDirection {
case up
case down
}
class TriangleView: UIView {
let size: CGFloat = 12.0
var direction: TriangleDirection = .up
var colour = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 0.7)
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
myInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
myInit()
}
func myInit() {
backgroundColor = UIColor.clear
}
// change to up or down
func set(direction: TriangleDirection, colour: UIColor = UIColor(red: 1.000, green: 1.000, blue: 1.000, alpha: 0.7)) {
self.direction = direction
self.colour = colour
setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
// general declarations
let context = UIGraphicsGetCurrentContext()
let origin = CGPoint(x: 0, y: 0)
// draw triangle
context!.saveGState()
context!.translateBy(x: origin.x, y: origin.y)
let trianglePath = UIBezierPath()
// draw depending on direction...
if direction == .up {
trianglePath.move(to: CGPoint(x: size/2, y: 0))
trianglePath.addLine(to: CGPoint(x: size, y: (size * 0.8)))
trianglePath.addLine(to: CGPoint(x: 0.0, y: (size * 0.8)))
} else {
trianglePath.move(to: CGPoint(x: size/2, y: (size * 0.8)))
trianglePath.addLine(to: CGPoint(x: 0, y: 0))
trianglePath.addLine(to: CGPoint(x: size, y: 0))
}
trianglePath.close()
colour.setFill()
trianglePath.fill()
context!.restoreGState()
}
override var intrinsicContentSize: CGSize {
return CGSize(width: size, height: size)
}
}
Issue Navigator warnings

my drawRect function will not update

I want the values for the colors of the circle to update when the variables(redd1,greenn1,bluee1) are changed by the steppers in my ViewController. The original circle is drawn by drawRect.
var redd1 = 0.0;
var greenn1 = 0.0;
var bluee1 = 0.0;
override init(frame: CGRect)
{
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect)
{
let circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
I tried creating a new function that draws a new circle on top of the old one. It is called by the stepper. The new function, updateColor(), receives the new value from the stepper. This partially works because it prints out the correct new values, but never draws the new circle.
func updateColor()
{
let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
circle.layer.cornerRadius = 50.0;
let startingColor = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat(greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle.backgroundColor = startingColor;
addSubview(circle);
}
I'm not so good in Swift, but that's a simple way how i wood do it:
var circle2: UIView
override func drawRect(rect: CGRect) {
circle2 = UIView(frame: CGRect(x: -25.0, y: 10.0, width: 100.0, height:100.0))
circle2.layer.cornerRadius = 50.0
let startingColor2 = UIColor(red: (CGFloat(redd1))/255, green: (CGFloat (greenn1))/255, blue: (CGFloat(bluee1))/255, alpha: 1.0)
circle2.backgroundColor = startingColor2;
addSubview(circle2);
}
#IBAction valueChanged(sender: UIStepper) {
changeColor(color(the Color you want), Int(the Int value you want the color to change at))
}
func changeColor(color: UIColor, number: Int) {
if stepper.value == number {
circle2.backgroundColor = color
}
}
You have to create a IBAction for the stepper of the type value changed and call the method for every color and to it belonging Int in the IBAction for the stepper. So every time + or - on the stepper is pressed the IBAction calls the changeColor method and the changeColor method tests which color should be set to circle2.