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.
Related
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.
I'm doing custom UIButton class and I want it to have gradient background. My gradient color is looking perfect but it goes out of button borders
My code:
class CustomButton: UIButton{
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
layer.cornerRadius = 25.0
layer.borderWidth = 1
gradientLayer.frame = layer.bounds
addShadow()
}
private lazy var gradientLayer: CAGradientLayer = {
let color1 = UIColor(red: 254.0/255.0, green: 79.0/255.0, blue: 50.0/255.0, alpha: 1.0).cgColor
let color2 = UIColor(red: 255.0/255.0, green: 26.0/255.0, blue: 107.0/255.0, alpha: 1.0).cgColor
let gradient = CAGradientLayer()
gradient.frame = layer.bounds
gradient.colors = [color1, color2]
gradient.startPoint = CGPoint(x: 0, y: 0.5)
gradient.endPoint = CGPoint(x: 1, y: 0.5)
gradient.locations = [0,1]
gradient.cornerRadius = 25
layer.insertSublayer(gradient, at: 0)
return gradient
}()
func addShadow(){
layer.shadowOpacity = 1
layer.shadowRadius = 1.0
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 1
layer.shadowOffset = CGSize(width: 0, height: 3)
}
}
And here's how it looks:
Remove gradientLayer.frame from setup and add in layoutSubviews.
Also set self.bounds instead of layer.bounds.
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = self.bounds // < Here
}
Note : Remove gradient.frame = layer.bounds from var gradientLayer. Not needed.
Just override layerClass to avoid resizing issue (Auto resize with constrains)
class CustomButton: UIButton{
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override class var layerClass: AnyClass {
CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
layer as! CAGradientLayer
}
func setup(){
gradientLayer.cornerRadius = 25.0
gradientLayer.borderWidth = 1
let color1 = UIColor(red: 254.0/255.0, green: 79.0/255.0, blue: 50.0/255.0, alpha: 1.0).cgColor
let color2 = UIColor(red: 255.0/255.0, green: 26.0/255.0, blue: 107.0/255.0, alpha: 1.0).cgColor
gradientLayer.colors = [color1, color2]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.locations = [0,1]
gradientLayer.shadowOpacity = 1
gradientLayer.shadowRadius = 1.0
gradientLayer.shadowColor = UIColor.black.cgColor
gradientLayer.shadowOpacity = 1
gradientLayer.shadowOffset = CGSize(width: 0, height: 3)
}
}
I'm been trying to create a shadow for my UIView. I looked around and found an extension for the CALayer class from this post. https://stackoverflow.com/a/48489506/9188318
So far its been working well for me until I try to put in a non 0 number for the spread.
With the spread being 0. This is the result
And here is the result using a spread of 1
And it gets even worse with a spread of 5
The problem that I'm having is that it doesn't have rounded corners and I have no idea how to fix this. Heres my code for the UIView that uses this view. The extension that is used to make the shadow is in the post above.
UIView code
class FileCalculateOperatorSelectionButton : UIView {
private var isActivated : Bool = false
//Background colors
private var unSelectedBackgroundColor : UIColor = UIColor(red: 178/255, green: 90/255, blue: 253/255, alpha: 1.0)
private var selectedBackgroundColor : UIColor = UIColor.white
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
private func commonInit() {
self.layer.cornerRadius = self.frame.height/2
self.backgroundColor = self.unSelectedBackgroundColor
let shadowColor = UIColor(red: 0xC8, green: 0xC6, blue: 0xC6)
//Change the spread argument here
self.layer.applySketchShadow(color: .black, alpha: 0.5, x: 0, y: 0, blur: 5, spread: 0)
}
}
Try setting the layer's masksToBounds property to true.
self.layer.masksToBounds = true
Change shadowPath in applySketchShadow function in CALayer extension like below;
// shadowPath = UIBezierPath(rect: rect).cgPath
shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
when spread: 1
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)
}
I have a stepper in my View Controller that updates variables(redd1, greenn1, bluee1) in my UIView. drawRect draws an initial circle and updateColor is meant to draw a new circle on top of it with an updated color. The variables get updated when I call updateColor and I know that they get passed through because when i have their values printed out in updateColor they are correct. updateColor won't draw a new circle.
class UIView1: UIView {
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);
}
func updateColor()
{
let circle = UIView(frame: CGRect(x: -25.0, y: 10.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);
}
}
Do not call addSubview in drawRect. Only use drawRect if you were going to draw a circle yourself (e.g. by calling stroke of a UIBezierPath). Or if you're going to add circles as subviews (as CAShapeLayer sublayers to the view's layer), the retire drawRect altogether.
But by calling addSubview in updateColor, you're not changing the color, but rather you're adding another subview every time. Likewise, by calling addSubview in drawRect, you're adding another subview there, too, every time the OS calls drawRect. For example, I ran your code, changing the color from black to red, to green, to blue, and triggered drawRect to be called again a few more times, and when I look at the view hierarchy in the view debugger, you can see all of the views in the view hierarchy:
This is a dangerous practice, because those subviews will add up over time, taking up memory.
If you're going to add subviews, I would suggest (a) drawRect is not the right place to be adding subviews; and (b) if you're determined to go the addSubview approach, decide whether you can remove the previous subviews before adding new ones.
Personally, if I wanted drawRect to draw circles of different colors, I would just stroke the UIBezierPath. For example:
class CircleView: UIView {
var redd1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var greenn1 : CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
var bluee1: CGFloat = 0.0 {
didSet {
setNeedsDisplay()
}
}
override func drawRect(rect: CGRect) {
let color = UIColor(red: redd1 / 255.0, green: greenn1 / 255.0, blue: bluee1 / 255.0, alpha: 1.0)
color.setStroke()
let path = UIBezierPath(arcCenter: CGPoint(x: 75, y: 75), radius: 50, startAngle: 0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)
path.lineWidth = 2
path.lineCapStyle = .Round
path.stroke()
}
}
By the way, you notice that we can retire updateColor entirely. With the above code, if you set either red, green, or blue, it will call setNeedsDisplay, which will trigger the view being redrawn automatically.
You report that the above isn't working. Well, I tried it with this code:
class ViewController: UIViewController {
#IBOutlet weak var circleView: CircleView!
#IBOutlet weak var redSlider: UISlider!
#IBOutlet weak var greenSlider: UISlider!
#IBOutlet weak var blueSlider: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
redSlider.value = Float(circleView.redd1) / 255.0
greenSlider.value = Float(circleView.greenn1) / 255.0
blueSlider.value = Float(circleView.bluee1) / 255.0
}
#IBAction func valueChangedForSlider(sender: UISlider) {
circleView.redd1 = 255.0 * CGFloat(redSlider.value)
circleView.greenn1 = 255.0 * CGFloat(greenSlider.value)
circleView.bluee1 = 255.0 * CGFloat(blueSlider.value)
}
}
And it worked fine:
As you mentioned that the both have the values which they should have.
The two circles which you are drawing are exactly on the same postion and both have exactly the same color according to your code snippet. How should they differ? Of course you can't see the two circles when they are having the same color and same position. They are overlayed.