How to add custom text on ScreenSaverView in Swift? - 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)
}

Related

Getting trouble when hovering a custom segmented control in swift

I tried to create custom segmented control which should change its look if you are hovering it. But it does not work as I expected.
Here's the code (I am sorry that it is that long but I already made it 200 lines shorter then it was):
class MySegmentedControl: UIView {
var state = MySegmentedControl_State.unknown
var buttons = [MySegmentedControl_ButtonView]()
func setUpView(){
let view = UIView(frame: self.frame)
view.frame.origin.x = 0
view.frame.origin.y = 0
view.addSubview(holderView())
self.addSubview(view)
}
func holderView() -> UIView{
let view = UIView(frame: CGRect(origin: CGPoint(x: 100, y: 0), size: CGSize(width: 490, height: 70)))
view.layer.borderWidth = 5
view.layer.borderColor = UIColor.gray.cgColor
view.layer.cornerRadius = view.frame.height / 2
view.layer.masksToBounds = true
view.clipsToBounds = true
view.backgroundColor = #colorLiteral(red: 0.5741485357, green: 0.5741624236, blue: 0.574154973, alpha: 1)
//place the first button
let button_1 = MySegmentedControl_ButtonView(text: "One", frame: CGRect(x: 0, y: 0, width: 163.3333, height: 70), type: .one, delegate: self)
//place the second Button
let button_2 = MySegmentedControl_ButtonView(text: "Two", frame: CGRect(x: 163.3333, y: 0, width: 163.3333, height: 70), type: .two, delegate: self)
//place the third Button
let button_3 = MySegmentedControl_ButtonView(text: "Three", frame: CGRect(x: 163.3333*2, y: 0, width: 163.3333, height: 70), type: .three, delegate: self)
buttons.append(button_1); buttons.append(button_2); buttons.append(button_3)
view.addSubview(button_1)
view.addSubview(button_2)
view.addSubview(button_3)
return view
}
class MySegmentedControl_ButtonView: UIView{
private var selected = false
private var hovering = false
var text = ""
private var type: MySegmentedControl_State
private var delegate: MySegmentedControl_ButtonView_Delegate
private var label = UILabel()
func setUpView(){
layer.cornerRadius = frame.height/2
let label = UILabel(frame: frame)
self.label = label
label.tintColor = .white
label.text = text
label.sizeToFit()
label.center = self.center
label.font = label.font.withSize(15)
let regionizer = UIHoverGestureRecognizer(target: self, action: #selector(didHover(_:)))
addGestureRecognizer(regionizer)
addSubview(label)
//set up the button
let button = UIButton(frame: bounds, primaryAction: UIAction(handler: { [self] action in
selected = true
delegate.shouldChangeState(to: type)
delegate.shouldDeselectOthers(without: text)
if !hovering{
backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.8)
}else{
self.backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.9435433103)
}
}))
addSubview(button)
}
func deselect(){
self.selected = false
if hovering{
backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}else{
backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}
}
#objc
func didHover(_ recognizer: UIHoverGestureRecognizer){
switch recognizer.state{
case .began, .changed:
hovering = true
if selected{
backgroundColor = #colorLiteral(red: 0.7540688515, green: 0.7540867925, blue: 0.7540771365, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}else{
backgroundColor = #colorLiteral(red: 0.2605174184, green: 0.2605243921, blue: 0.260515637, alpha: 0.15)
UIView.animate(withDuration: 0.2) {[self]in
label.frame.origin.y = 10
label.font = label.font.withSize(12)
}
}
case .ended:
hovering = false
if selected{
self.backgroundColor = #colorLiteral(red: 0.9961533629, green: 0.9931518435, blue: 1, alpha: 0.2)
UIView.animate(withDuration: 0.2) {[self]in
label.center.y = center.y
label.font = label.font.withSize(15)
}
}else{
self.backgroundColor = .clear
UIView.animate(withDuration: 0.2) {[self]in
label.center.y = center.y
label.font = label.font.withSize(15)
}
}
default:break
}
}
init(text: String, frame: CGRect, type: MySegmentedControl_State, delegate: MySegmentedControl_ButtonView_Delegate){
self.type = type
self.delegate = delegate
super.init(frame: frame)
self.text = text
setUpView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
extension MySegmentedControl: MySegmentedControl_ButtonView_Delegate{
func shouldDeselectOthers(without: String) {
for button in buttons{
if button.text != without{
button.deselect()
}
}
}
func shouldChangeState(to state: MySegmentedControl_State) {
self.state = state
}
}
protocol MySegmentedControl_ButtonView_Delegate{
func shouldDeselectOthers(without: String)
func shouldChangeState(to state: MySegmentedControl_State)
}
enum MySegmentedControl_State: String{
case unknown = "Non specific case avalaible"
case one = "One selected"
case two = "Two selected"
case three = "Three selected"
}
But the second of my buttons is displayed as the third and the the third does not get displayed, but if I hover the place where button two SHOULD be, it still gets hovered.
Here is a shout video:
Video showing problems with this code
My App is running on MacOS with MacCatalyst
In setUpView() inside class MySegmentedControl_ButtonView, you are trying to set the label's position with:
label.center = self.center
However, self.center is relative to self's superview. So your labels are being shifted.
If you change that line to:
label.center = CGPoint(x: self.bounds.midX, y: self.bounds.midY)
the label centering will be correct.
As I asked in my comment though... why aren't you using auto-layout? It would make things much easier (and much more flexible).

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

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.

having difficulties calling functions in a different view controller

I'm having some difficulties calling a function in a view controller that is different than the one it was declared in. I've tried some methods from different posts on the site, but none of them are working for me.
class Housekeeping: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
var balance = 387563 //remove later, arbitrary value for testing
var username = "kaplan"
#objc func addTop() {
let balanceLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 150, height: 30))
balanceLabel.text = "\(balance)"
balanceLabel.textAlignment = .center
balanceLabel.font = UIFont.boldSystemFont(ofSize: 20)
balanceLabel.center = CGPoint(x: self.view.center.x*2-95, y: 50)
balanceLabel.textColor = UIColor(displayP3Red: 160/255, green: 160/255, blue: 160/255, alpha: 1.0)
view.addSubview(balanceLabel)
let genechipImg = UIImageView(image: UIImage(named: "genechip"))
genechipImg.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
genechipImg.center = CGPoint(x: balanceLabel.center.x + 60, y: balanceLabel.center.y)
view.addSubview(genechipImg)
let usernameLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 130, height: 30))
usernameLabel.text = "Dr. \(username)"
usernameLabel.textAlignment = .center
usernameLabel.font = UIFont.boldSystemFont(ofSize: 20)
usernameLabel.center = CGPoint(x: 80, y: 50)
usernameLabel.textColor = UIColor(displayP3Red: 160/255, green: 160/255, blue: 160/255, alpha: 1.0)
view.addSubview(usernameLabel)
}
}
This addTop function was declared in a view controller named Housekeeping and I'm trying to call it on a second view controller named GeneLibrary but I can't figure out how. As you can see, the function doesn't do much just adds two labels at the top, which I'm trying to add on a subsequent view controller. Here's the second view controller I'm trying to pass it to:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("gene library scene running")
}
}
If anybody could please provide some insight I'd really appreciate it. Stay safe everybody, thanks!
Declare your function as static:
static func addTop() { print("hello") }
Then call it like so:
Housekeeping.addTop()
Without a context of your app, The solution is:
GeneLibrary().addTop()
This will get to the function and print hello, but I believe this is not your intention, as GeneLibrary() creates a new instance from GeneLibrary view controller, while there might already be another instance that you want to call in order to do what you want.
if you can provide more details I'll be glad to help.
UPDATE
as the second view controller is being presented:
let housekeepingVC = presentingViewController as? Housekeeping
housekeepingVC.addTop()
this will do the job

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
}
}

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.