How do I draw a rectangle with equal line widths into an NSStatusItemButton? - swift

The following code draws a rectangle with NSBezierPath into an NSImage, which is then set as the image for an NSStatusItemButton.
import Cocoa
class ViewController: NSViewController {
let statusItem: NSStatusItem = NSStatusBar.system.statusItem(
withLength: NSStatusItem.squareLength)
override func viewDidLoad() {
super.viewDidLoad()
let imageSize = NSSize.init(width: 18.0, height: 18.0)
let statusItemImage = NSImage(
size: imageSize,
flipped: false,
drawingHandler: { (dstRect: NSRect) -> Bool in
NSColor.black.setStroke()
let path = NSBezierPath()
path.appendRect(NSRect(
x: NSMinX(dstRect),
y: NSMinY(dstRect),
width: 10,
height: 10))
path.stroke()
return true
})
statusItem.button?.image = statusItemImage
}
override var representedObject: Any? {
didSet {
}
}
}
In the menu bar, it looks like this:
The left and bottom edge of the rectangle have a different width than the right and top edge.
How do I get a rectangle with equal line widths?

You have to set a lineWidth for your path:
/// The new rectangle
let new = CGRect(origin: dstRect.origin,
size: .init(width: 10, height: 10))
let path = NSBezierPath(rect: new)
// The line width size
path.lineWidth = 0.1
path.stroke()

Related

How to generate a custom UIView that is above a UIImageView that has a circular hole punched in the middle that can see through to the UIImageView?

I am trying to punch a circular hole through a UIView that is above a UIImageView, whereby the hole can see through to the image below (I would like to interact with this image through the hole with a GestureRecognizer later). I have 2 problems, I cannot get the circular hole to centre to the middle of the UIImageView (it is currently centred to the top left of the screen), and that the effect that I am getting is the opposite to what i am trying to achieve (Everything outside of the circle is visible). Below is my code. Please can someone advise?
Result:
class UploadProfileImageViewController: UIViewController {
var scrollView: ReadyToUseScrollView!
let container: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imgView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.image = UIImage.init(named: "soldier")!
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
var overlay: UIView!
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
private func setup(){
view.backgroundColor = UIColor.white
setupViews()
}
override func viewDidLayoutSubviews() {
overlay.center = imgView.center
print("imgView.center: \(imgView.center)")
overlay.layer.layoutIfNeeded() // I have also tried view.layoutIfNeeded()
}
private func setupViews(){
let s = view.safeAreaLayoutGuide
view.addSubview(imgView)
imgView.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
imgView.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
imgView.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
imgView.heightAnchor.constraint(equalTo: s.heightAnchor, multiplier: 0.7).isActive = true
overlay = Overlay.init(frame: .zero, center: imgView.center)
print("setup.imgView.center: \(imgView.center)")
view.addSubview(overlay)
overlay.translatesAutoresizingMaskIntoConstraints = false
overlay.topAnchor.constraint(equalTo: s.topAnchor).isActive = true
overlay.leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
overlay.trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
overlay.bottomAnchor.constraint(equalTo: imgView.bottomAnchor).isActive = true
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
class Overlay: UIView{
var path: UIBezierPath!
var viewCenter: CGPoint?
init(frame: CGRect, center: CGPoint) {
super.init(frame: frame)
self.viewCenter = center
setup()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup(){
backgroundColor = UIColor.black.withAlphaComponent(0.8)
guard let path = createCirclePath() else {return}
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.fillRule = CAShapeLayerFillRule.evenOdd
self.layer.mask = shapeLayer
}
private func createCirclePath() -> UIBezierPath?{
guard let center = self.viewCenter else{return nil}
let circlePath = UIBezierPath()
circlePath.addArc(withCenter: center, radius: 200, startAngle: 0, endAngle: deg2rad(number: 360), clockwise: true)
return circlePath
}
private func deg2rad( number: Double) -> CGFloat{
let rad = number * .pi / 180
return CGFloat.init(rad)
}
}
CONSOLE:
setup.imgView.center: (0.0, 0.0)
imgView.center: (207.0, 359.0)
Try getting rid of the overlay you have and instead add the below UIView. It's basically a circular UIView with a giant black border, but it takes up the whole screen so the user can't tell. FYI, you need to use .frame to position items on the screen. The below puts the circle in the center of the screen. If you want the center of the image, replace self.view.frame with self. imgView.frame... Play around with circleSize and borderSize until you get the circle size you want.
let circle = UIView()
let circleSize: CGFloat = self.view.frame.height * 2 //must be bigger than the screen
let x = (self.view.frame.width / 2) - (circleSize / 2)
let y = (self.view.frame.height / 2) - (circleSize / 2)
circle.frame = CGRect(x: x, y: y, width: circleSize, height: circleSize)
let borderSize = (circleSize / 2) * 0.9 //the size of the inner circle will be circleSize - borderSize
circle.backgroundColor = .clear
circle.layer.cornerRadius = circle.frame.height / 2
circle.layer.borderColor = UIColor.black.cgColor
circle.layer.borderWidth = borderSize
view.addSubview(circle)

Programmatically Setting AutoresizingMask to NSView Object

I wonder why it always fails whenever I try to set the autoresizingMask to an NSView object programmatically? In the following lines of code, I have a tiny, blue NSVIew object (topLeftView) set at the top-left corner of its parent container.
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var panView: PanView!
override func viewDidLoad() {
super.viewDidLoad()
makeImageContainer(point: CGPoint.zero)
}
func makeViewContainer(point: CGPoint) {
let myView = NSView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 150.0, height: 150.0))
myView.wantsLayer = true
if let myLayer = myView.layer {
myLayer.backgroundColor = NSColor.orange.cgColor
}
panView.addSubview(myView) // panView is an IBOutlet-connected `NSView` object.
/* tiny, blue `NSView` object */
let topLeftView = NSView(frame: CGRect(origin: CGPoint(x: 0.0, y: 150.0 - 6.0), size: CGSize(width: 6.0, height: 6.0)))
topLeftView.wantsLayer = true
topLeftView.autoresizingMask = [.minXMargin, .maxYMargin]
if let layer = topLeftView.layer {
layer.backgroundColor = NSColor.blue.cgColor
}
myView.addSubview(topLeftView)
}
}
So it has minXMargin and maxYMargin autoresizingMasks.
Now, I want to click on a push button to resize this orange NSView container object to see if the tiny, blue NSView object will stick at the top-left corner.
#IBAction func testClicked(_ sender: NSButton) {
for i in 0..<panView.subviews.count {
let subView = panView.subviews[i]
if i == 4 {
subView.setFrameSize(CGSize(width: 200.0, height: 200.0))
}
}
}
And the tiny guy has moved by 50 points to the right and 50 points to the bottom. What am I doing wrong? Thanks.

change position UiButton with Animation Swift

i have a button in ViewController and i changed button position when clicked on that .
the question it is how to can move button to new position when Clicked with animation ?
this is view Controller :
override func viewDidLoad() {
super.viewDidLoad()
let X = 50
let Y = 100
let WIDTH = 100
let HEIGHT = 100
movable_Button.frame = CGRect(x: X, y: Y, width: WIDTH , height: HEIGHT)
}
this is change position method When clicked :
#IBAction func movable_Button_DidTouch(_ sender: Any) {
movable_Button.frame = CGRect(x: X + 150, y: Y + 150, width: 100, height: 100)
}
Let's use UIView animate block
#IBAction func movableButtonDidTouch(_ sender: Any) {
// Set initial frame here
UIView.animate(withDuration: 0.35) {
// Set final frame here
}
}
Please notice to remove underline (_) in function name, it's not the convention of Swift, use camel case instead.
Update
class ViewController: UIViewController {
var positions = [CGPoint]()
let width = 100.0
let height = 50.0
var index = 0
lazy var button: UIButton = {
let theButton = UIButton(type: .system)
theButton.frame = CGRect(x: 50, y: 50, width: self.width, height: self.height)
theButton.backgroundColor = .red
return theButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.button)
for index in 1...4 {
let point = CGPoint(x: 50 * index, y: 50 * index)
self.positions.append(point)
}
}
#IBAction func changeButtonPosition(_ sender: AnyObject) {
UIView.animate(withDuration: 0.35) { [weak self] in
guard let self = self else { return }
self.index += 1
let point = self.positions[self.index % 4]
self.button.frame = CGRect(origin: point, size: CGSize(width: self.width, height: self.height))
}
}
}

Drawing an NSBezierPath Shape with NSView Subclass

I'm trying to draw a shape that is created with NSBezierPath on an NSView canvas. I've created a subclass of NSView.
// NSView //
import Cocoa
class DisplayView: NSView {
var path: NSBezierPath
var fillColor: NSColor
var strokeColor: NSColor
var weight: CGFloat
init(frame: CGRect, path: NSBezierPath, fillColor: NSColor, strokeColor: NSColor, weight: CGFloat){
self.path = path
self.fillColor = fillColor
self.strokeColor = strokeColor
self.weight = weight
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ dirtyRect: NSRect) {
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
}
}
// NSViewController //
import Cocoa
class ViewController: NSViewController {
// MARK: - IBOutlet
#IBOutlet weak var canvasView: NSView!
override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
let path = Shapes.circle(maxSize: 100) // a path from a separate class
let rect = CGRect(x: 20, y: 20, width: 200, height: 200)
let pathView = DisplayView(frame: rect, path: path, fillColor: NSColor.green, strokeColor: NSColor.white, weight: 6.0)
canvasView.addSubview(pathView)
}
}
And I get the following result. How come the edges are broken by half the line weight on two sides? The path object is only a size of 100 pts x 100 pts. Thanks.
UPDATE
The following is the code for making a path object.
class Shapes {
static func circle(maxSize: CGFloat) -> NSBezierPath {
let oval = NSBezierPath.init(ovalIn: CGRect(x: 0.0 * maxSize, y: 0.0 * maxSize, width: 1.0 * maxSize, height: 1.0 * maxSize))
oval.close()
return oval
}
}
You have created your bezier path with an origin of 0,0. As a result, the thicker border gets clipped by the view it is rendered in (your DisplayView class).
You need to create your path so the origin is (weight / 2 + 1) instead of 0.
Or you can apply a translate transform to the graphics context and shift the origin by (weight / 2 + 1.
override func draw(_ dirtyRect: NSRect) {
let ctx = NSGraphicsContext.current!.cgContext
ctx.saveGState()
let offset = weight / 2 + 1
ctx.translateBy(x: offset, y: offset)
path.lineWidth = weight
fillColor.set()
path.fill()
strokeColor.set()
path.stroke()
ctx.restoreGState()
}

NSImageView mouseDragged Value pass back to windowController

Can anyone please show me how to get the position X and Y back to my WindowController NSTextField (testTextX and testTextY)?
I created a windowController, NSImage and NSTextField programmatically. I added an image "dot" to the NSView and would like the "dot" image draggable and return the position X and Y.
I manage to have the image added and draggable but I do not know how can I pass the position X and Y on mouseDragged back to the NSTextField at my WindowController (testTextX and testTextY)?
The current display as below, when click on the "dot" image, it is draggrable to any position.
I can print out the position X and Y from the NSImageView func mouseDragged(theEvent: NSEvent) when image was dragged.
I am stuck at the how to pass the position X and Y back to WindowController.
My WindowController.swift code as below:
public class MyWindowController: NSWindowController, NSWindowDelegate {
// MARK: Constants
// 1. Define min&max window size
var fWidth:CGFloat = 0
var fHeight:CGFloat = 0
var vWidth:CGFloat = 0
var vHeight:CGFloat = 0
var blankPage: NSView!
var viewDotImg: NSImageView!
var testTextX: NSTextField!
var testTextY: NSTextField!
var modalWinHeight: CGFloat = 0
var m_window:NSWindow = NSWindow()
// MARK: Initializer
init(){
super.init(window: self.m_window)
}
init(channelId:Int){
super.init(window:self.m_window)
// Get the window full width and height
fWidth = (NSScreen.mainScreen()?.frame.width)!
fHeight = (NSScreen.mainScreen()?.frame.height)!
// Get the window visible width and height
vWidth = (NSScreen.mainScreen()?.visibleFrame.width)!
vHeight = (NSScreen.mainScreen()?.visibleFrame.height)!
// Get the position X and Y for Window
let winPosX: CGFloat = fWidth - vWidth
let winPosY: CGFloat = fHeight - vHeight
// Divide the height by 3
let avgHeight: CGFloat = vHeight / 3
// Set the max window height as avgHeight * 1.3
let modalWinHeight: CGFloat = avgHeight * 1.3
// Set the window frame
self.m_window = NSWindow(contentRect: NSMakeRect(winPosX, winPosY, vWidth, modalWinHeight),
styleMask: NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask,
backing: NSBackingStoreType.Buffered, `defer`: false);
// Window delegate
self.m_window.delegate = self
// Add a blank NSView with Gradian background colour
blankPage = CGradiantBlank(x: x, y:y, width:width, height: height)
self.window?.contentView = blankPage
// Add Image Dot to NSView
viewDotImg = DragNSImageView(x: 200, y: 200, width: 10, height: 10)
// Add Two TextField to NSView
testTextX = TextLabel(x: 200, y: 10, width: 100, height: 24, value: "100")
testTextY = TextLabel(x: 305, y: 10, width: 100, height: 24, value: "100")
blankPage.addSubview(viewDotImg)
blankPage.addSubview(testTextX)
blankPage.addSubview(testTextY)
}
func updTestXY(x:Double, y:Double){
testTextX.stringValue = String(x)
testTextY.stringValue = String(y)
}
override public init(window: NSWindow?) {
super.init(window: window)
}
required public init?(coder:NSCoder){
super.init(coder: coder)
}
}
My NSImageView code as below:
public class DragNSImageView: NSImageView {
private var xWidth: CGFloat = 0
private var xHeight: CGFloat = 0
init(x:CGFloat, y:CGFloat, width:CGFloat, height:CGFloat, chId: Int) {
super.init(frame: NSRect(x: x,y: y,width: width,height: height))
super.imageScaling = NSImageScaling.ScaleAxesIndependently
super.image = NSImage(named: "dot-10")
xWidth = width
xHeight = height
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
override public func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
}
public override func mouseDragged(theEvent: NSEvent) {
super.mouseDown(theEvent)
let location = theEvent.locationInWindow
let pX: CGFloat = location.x
let pY: CGFloat = location.y
super.frame = NSRect(x: pX,y: pY,width: xWidth,height: xHeight)
Swift.print("X: \(location.x)" Y: \(location.y))
/* How to pass this location.x and location.y back to WindowController func updateTestXY()?
updTestXY(Double(location.x), y:Double(location.y))
*/
}
}
My NSTextField code as below:
public class TextLabel: NSTextField {
// MARK: Initializer
init(){
super.init(frame: NSRect(x: 0, y: 0, width: 90, height: 10))
}
convenience init(x:CGFloat, y:CGFloat, width:CGFloat, height:CGFloat, value:String){
self.init()
super.wantsLayer = true
super.backgroundColor = NSColor.darkGrayColor()
super.textColor = NSColor.whiteColor()
super.stringValue = value
super.frame = NSRect(x: x, y: y, width: width, height: height)
}
required public init?(coder: NSCoder) {
super.init(coder: coder)
}
override public func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
}
}
Ok, I found the solution.
let mainWindow = self.window?.windowController as! MyWindowController
mainWindow.updXY(location.x, y:location.y, refName: refName)
If more than one "dot" image, then add the following code to the NSImageView init()
super.identifier = "ReferenceName1"
Now, go to NSImageView mouseDragged(theEvent: NSEvent) method and add the following line:
let refName = super.identifier
This will be use at the MyWindowController to detect which "dot" image was dragged when calling the specified function. In my case, I call the
updXY(location.x, y:location.y, refName: refName)
So, at MyWindowController, I use the following to do the work.
public func updXy(x: CGFloat, y: CGFloat, refName: String){
if refName == "ReferenceName1" {
}else if refName == "ReferenceName2" {
}
}