NSView with NSBezierPath not updating after window resize - swift

I probably miss something obvious, but...
I have a simple NSView:
class TestView : NSView {
override func drawRect(dirtyRect: NSRect) {
let path = NSBezierPath()
path.moveToPoint(NSPoint(x: 0 ,y: 0))
path.lineToPoint(NSPoint(x: frame.size.width, y: 0))
path.lineToPoint(NSPoint(x: frame.size.width, y: frame.size.height))
path.lineToPoint(NSPoint(x: 0 ,y: frame.size.height))
path.lineToPoint(NSPoint(x: 0 ,y: 0))
path.lineWidth = 1
path.stroke()
}
}
This is placed sole in a window. The window controller has
func windowDidEndLiveResize(notification: NSNotification) {
var frame = textFrame
frame.size.height = window!.frame.size.height - 60
frame.size.width = window!.frame.size.width - 40
field.frame = frame
field.needsDisplay = true
}
where frame is the size of the frame on window load and field is my TestView. So when I start my app it will draw the frame nicely as expected:
But when scaled down
it appears that the view is not erased and it leaves artifacts on the window.
It's probably something trivial, but I'm currently blind.

Try getting the superview to redraw and clear that visual artifact using field.superview?.needsDisplay = true.
Depending on your goals, you might want to use windowDidResize(notification: NSNotification) instead which will continually update the view while the window is being resized.

Related

Tried making a custom ScrollView, but instead of scrolling it's spamming up and down

I tried to create some kind of timeline (with the Vector Illustrator mentality), using UIBezier and UI Label (kind of like in the calendar app) and then use UIPanGestureRecognizer to scroll it up and down. But whenever I scroll it in the simulator, it multiplies itself instead of moving like the images below (I use setNeedsDisplay as the scrollValue changes to redraw the whole mechanism). This is probably a small mistake a I did or maybe my code doesn't work.
I know I could use a UIScrollView or UITableView instead, but I tried making this as a small challenge as a custom made table because using pre-made objects feels limiting for someone like me who is used to CAD drawing or Vector Illustrator.
This image explains what happens in the Simulator:
The code I used is below:
import UIKit
class ViewController: UIViewController {
var tlobject = TimelineView()
let gesto = UIPanGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
// ===== Add TimelineView Object to view
let TLObjectFrame = CGRect(x: 0, y: 40, width: 100, height: 100)
tlobject = TimelineView(frame: TLObjectFrame)
view.addSubview(tlobject)
// ===== ADD TOUCH GESTURE =====
gesto.addTarget(self, action: #selector(touchinput))
view.addGestureRecognizer(gesto)
}
var touchStartLocation: Int = 0
var scrollDistance: Int = 0
var lastScrollDistance: Int = 0
//The following func calculates the distance scrolled/travelled by Touch gesture on the YAxis and sends the result value (scrollDistance) to the Timeline mechanism where it defines the Yposition of every UIBezier. Thanks to Mitchell Hudson on Youtube for helping me figure out how to do it on his Tutorial "06 11 touches value"
#objc func touchinput (sender: UIPanGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
touchStartLocation = Int(sender.location(in: view).y)
lastScrollDistance = scrollDistance
}
if sender.state == UIGestureRecognizer.State.changed {
let touchEndLocation = Int(sender.location(in: view).y)
let currentScrollDistance = touchEndLocation - touchStartLocation
print("deltaY", currentScrollDistance)
var newScrollDistance = lastScrollDistance + currentScrollDistance
scrollDistance = newScrollDistance
tlobject.totalScrollDistance = scrollDistance //send scrollValue to TimelineView
}
if sender.state == UIGestureRecognizer.State.ended {
print("lastScrollDistance", lastScrollDistance)
print("scroll Distance", scrollDistance)
}
}
}
//Created a new View with the TimeLine mechanism
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet{
setNeedsDisplay() //this gets called everytime UIgesture position changes
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
timelinemechanism()
}
func timelinemechanism() {
let lineElements: Array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let spacing: Int = 30
let scrollDistance: Int = totalScrollDistance
let totalElements: Int = lineElements.count
for n in 1...totalElements {
//Get UILabel/UILine Yposition on screen = Array index number * the spacing + scroll distance by touch pan gesture
let yPosition = lineElements[n - 1] * spacing + scrollDistance
let linepath = UIBezierPath()
linepath.move(to: CGPoint(x: 60, y: yPosition))
linepath.addLine(to: CGPoint(x: 300, y: yPosition))
let lineshape = CAShapeLayer()
lineshape.path = linepath.cgPath
lineshape.strokeColor = UIColor.blue.cgColor
//lineshape.fillColor = UIColor.white.cgColor
//lineshape.lineWidth = 1
self.layer.addSublayer(lineshape)
let hourlabel = UILabel()
hourlabel.frame = CGRect(x: 5, y: yPosition - 20, width: 45, height: 40)
hourlabel.text = "\(n):00"
//hourlabel.font = UIFont(name: "Avenir-Claro", size: 12)
hourlabel.textColor = UIColor.blue
hourlabel.textAlignment = NSTextAlignment.right
self.addSubview(hourlabel)
}
}
}
Inside draw you only have to draw something. You add new subviews/sublayers and do not remove old ones.
Creating a new view every time you change a frame is very resource-intensive. And you don't need that, because you have the same views, you only need to change the position.
Instead, you can create your views at start and use layoutSubviews to update your views positions:
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet{
setNeedsLayout() //this gets called everytime UIgesture position changes
}
}
private var lastLayoutTotalScrollDistance: Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.clear
createTimelinemechanism()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var lineShapes = [CAShapeLayer]()
var hourLabels = [UILabel]()
override func layoutSubviews() {
super.layoutSubviews()
let offset = totalScrollDistance - lastLayoutTotalScrollDistance
lastLayoutTotalScrollDistance = totalScrollDistance
lineShapes.forEach { lineShape in
lineShape.frame.origin.y += CGFloat(offset)
}
hourLabels.forEach { hourLabel in
hourLabel.frame.origin.y += CGFloat(offset)
}
}
func createTimelinemechanism() {
let lineElements: Array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let spacing: Int = 30
let totalElements: Int = lineElements.count
for n in 1...totalElements {
//Get UILabel/UILine Yposition on screen = Array index number * the spacing + scroll distance by touch pan gesture
let yPosition = lineElements[n - 1] * spacing
let linepath = UIBezierPath()
linepath.move(to: CGPoint(x: 60, y: yPosition))
linepath.addLine(to: CGPoint(x: 300, y: yPosition))
let lineshape = CAShapeLayer()
lineshape.path = linepath.cgPath
lineshape.strokeColor = UIColor.blue.cgColor
//lineshape.fillColor = UIColor.white.cgColor
//lineshape.lineWidth = 1
// disable default layer position animation
lineshape.actions = [
"position": NSNull(),
]
self.layer.addSublayer(lineshape)
lineShapes.append(lineshape)
let hourlabel = UILabel()
hourlabel.frame = CGRect(x: 5, y: yPosition - 20, width: 45, height: 40)
hourlabel.text = "\(n):00"
//hourlabel.font = UIFont(name: "Avenir-Claro", size: 12)
hourlabel.textColor = UIColor.blue
hourlabel.textAlignment = NSTextAlignment.right
self.addSubview(hourlabel)
hourLabels.append(hourlabel)
}
}
}
More generally, you can just list all the subviews/sublayers and not keep them in separate containers.
I spent a bit more time with your question since my first thought was wrong. Let me start by saying that your approach here is not the right way to go about this. But it looks to me like you're playing with different aspects of the framework just to learn your way around and I can respect that. I spent many years working on as vector drawing program (Macromedia FreeHand) and even wrote a book about drawing with Quartz 2D back in 2006 so I understand the desire to draw it yourself.
I've reworked your example using "raw" drawing at the CGContext level. I was playing with your code in a Playground so I restructured the view creation a bit too (just so it shows up in the Playground nicely). You should be able to copy and paste this into an iOS playground and see the results.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class ViewController: UIViewController {
let gesto = UIPanGestureRecognizer()
let timelineView = TimelineView()
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = true
self.view.backgroundColor = UIColor.red
self.view.bounds = CGRect(x:0, y:0, width: 320, height: 700)
// ===== Add TimelineView Object to view
view.addSubview(timelineView)
timelineView.frame = CGRect(x: 20, y: 20, width: 280, height: 660)
timelineView.backgroundColor = UIColor.white
debugPrint(timelineView.bounds)
// ===== ADD TOUCH GESTURE =====
gesto.addTarget(self, action: #selector(touchinput))
timelineView.addGestureRecognizer(gesto)
}
var touchStartLocation: Int = 0
var scrollDistance: Int = 0
var lastScrollDistance: Int = 0
//The following func calculates the distance scrolled/travelled by Touch gesture on the YAxis and sends the result value (scrollDistance) to the Timeline mechanism where it defines the Yposition of every UIBezier. Thanks to Mitchell Hudson on Youtube for helping me figure out how to do it on his Tutorial "06 11 touches value"
#objc func touchinput (sender: UIPanGestureRecognizer) {
if sender.state == UIGestureRecognizer.State.began {
touchStartLocation = Int(sender.location(in: view).y)
lastScrollDistance = scrollDistance
}
if sender.state == UIGestureRecognizer.State.changed {
let touchEndLocation = Int(sender.location(in: view).y)
let currentScrollDistance = touchEndLocation - touchStartLocation
print("deltaY", currentScrollDistance)
scrollDistance = lastScrollDistance + currentScrollDistance
timelineView.totalScrollDistance = scrollDistance //send scrollValue to TimelineView
}
if sender.state == UIGestureRecognizer.State.ended {
print("lastScrollDistance", lastScrollDistance)
print("scroll Distance", scrollDistance)
}
}
}
//Created a new View with the TimeLine mechanism
class TimelineView: UIView {
var totalScrollDistance: Int = 0 {
didSet {
setNeedsDisplay() //this gets called everytime UIgesture position changes
}
}
override func draw(_ rect: CGRect) {
if let cgContext = UIGraphicsGetCurrentContext() {
drawTimeline(cgContext: cgContext)
}
}
func drawTimeline(cgContext: CGContext) {
let numElements = 10
let spacing = 30
let scrollDistance = totalScrollDistance
for n in 0..<numElements {
let yPosition = n * spacing + scrollDistance
cgContext.saveGState()
cgContext.setLineWidth(1.0)
cgContext.setStrokeColor(UIColor.blue.cgColor)
cgContext.move(to: CGPoint(x: 60, y: yPosition))
cgContext.addLine(to: CGPoint(x: 300, y: yPosition))
cgContext.strokePath()
let label : NSString = "\(n):00" as NSString
label.draw(at: CGPoint(x: 5, y: yPosition - 20),
withAttributes: [.foregroundColor : UIColor.blue])
cgContext.restoreGState()
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()
The drawRect of the custom view grabs the current CGContext and passes it to the routine that does the drawing. Using something like UIBezierPath will work, of course (you saw that it did) but it has overhead (creating an actual object, copying the object into the context graphics state on each drawing, etc) that you don't necessarily need.
I'm not sure what you were doing with CAShapeLayer. You'd typically use that if you had a shape that you want to animate around the screen. I suppose you felt that, in scrolling, you might want to do that. But again this is something where you'd want to create the shape layer outside of the drawing path, keep ahold of it, manipulate it outside of the drawing path, then let the system handle worry about putting it on the screen appriopriately.
Your instincts on text are pretty good. You really don't want to handle Text drawing yourself in a system as complex as iOS. There's Unicode issues, glyph substitution, positional forms, ligatures, bi-di text... a whole host of challenges for drawing text on iOS that it's best to leave to things like UILabel. But you want to keep building your view hierarchy separate from drawing in your view hierarchy. drawRect can be called any time even a pixel of your view needs to be redrawn and adding a new subview each time is not the best way to go. In my reworked example, I'm drawing the text using NSString - It's still not the "right" way to do it but it's fairly low level while still giving the framework a chance to do some of the text handling.
In the end you would want to work with the frameworks instead of against them. You'd want to use something like UIScrollView because it will handle a thousand details (bouncing at the boundaries, ease-in/ease-out animation, touch point tracking, fast and slow scrolling, etc) but for a learning experience your code is just fine and I hope you enjoy working with iOS more!

CAGradientLayer not fit perfectly on UITableViewCell bounds

i am developing simple UITableView with custom UITableViewCell with Xib file
i have added MainView and pin it to ContentView in order to add all my views inside MainView
as shown below
UITableViewCell layout
when i try to add CAGradientLayer to MainView it doesnt fit perfectly on mainView bounds as i show below
whats happening on simulator
i use this piece of code to add CAGradientLayer
override func awakeFromNib() {
super.awakeFromNib()
let gradient = CAGradientLayer()
mainView.layer.cornerRadius = 10
gradient.frame = mainView.frame
gradient.colors = [ColorCompatibility.gradEnd.cgColor, ColorCompatibility.gradStart.cgColor]
gradient.endPoint = CGPoint(x: 0.5, y: 1)
gradient.startPoint = CGPoint(x: 0.5, y: 0)
// gradient.cornerRadius = 20
gradient.shadowColor = ColorCompatibility.systemBackground.cgColor
gradient.shadowOpacity = 0.8
gradient.shadowOffset = .zero
gradient.shadowRadius = 5
gradient.shadowPath = UIBezierPath(roundedRect: mainView.layer.bounds, cornerRadius: 20).cgPath
gradient.shouldRasterize = true
gradient.rasterizationScale = UIScreen.main.scale
contentView.layer.insertSublayer(gradient, at: 0)
}
how can i fix this?
Two issues here. The first is that in awakeFromNib the cell (and its content view) most likely doesn't have the correct frame so you could use another life cycle hook where the frame is correct but then you would run into problems when the device is rotated so the cell frame changes at runtime.
I would suggest to update the gradient layers frame in layoutSublayers(of:). Let's assume you created a property for the layer called gradient:
override func layoutSublayers(of layer: CALayer) {
super.layoutSublayers(of: layer)
gradient.frame = mainView.bounds
}

Customising UItababbar swift

Can anyone point me how could I achieve such design in the UITabbar. I have tried adding the back-ground Image, but that does not look like the design. Here the curve is extended beyond the frame of UITabbar, not sure how to add this views on top of active tabbar.
Creating a custom TabBar from UITabBarController can be solved the problem. Instead of adding a direct image to the Tabbar, use an on the fly image using UIGraphicsBeginImageContext for selectedTabBackgroundImage.
Create the image.
Clip the top part round in the image
Here is the example of the code.
import UIKit
class CustomTabBarViewController: UITabBarController {
var topClipSize: CGFloat = 24.5 //Adjust based on the number of tabbar
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let singleTabWidth: CGFloat = self.tabBar.frame.size.width / CGFloat((self.tabBar.items?.count)!)
let singleTabSize = CGSize(width:singleTabWidth , height: self.tabBar.frame.size.height)
// Create the backgound image
let selectedTabBackgroundImage: UIImage = self.imageWithColor(color: .blue, size: singleTabSize)
// Clip the top
self.tabBar.selectionIndicatorImage = selectedTabBackgroundImage.roundTopImage(topClipSize: topClipSize)
}
func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
if size.height > 55 {
topClipSize = 30.0 // iPhone 8 tabbar height is 53 and iPnone X is 83 - We need more space on top.
}
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height + topClipSize)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension UIImage {
func roundTopImage(topClipSize: CGFloat) -> UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let rectBounds: CGRect = CGRect(x: rect.origin.x, y: rect.origin.y + (topClipSize * 2), width: rect.size.width, height: rect.size.height - (topClipSize * 2))
let ovalBounds: CGRect = CGRect(x: rect.origin.x - topClipSize, y: rect.origin.y, width: rect.size.width + (topClipSize * 2), height: rect.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
rectPath.addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
}
Here is the output:
It's not really possible to elegantly change the native UITabBar's appearance to that extent. Your options are to create a custom container view controller that acts like a UITabBarController, or just hide the default tab bar and implement your own view in that space.
Even though it's less elegant because you'd be just throwing a view on top of the default tab bar, I actually like that method because you retain the benefits of the native UITabBarController (calling self.tabBarController? from its view controllers, it already adjusts layout margins, etc).
To do this, in your subclass of UITabBarController hide the tabBar:
self.tabBar.isHidden = true
self.tabBar.alpha = 0
Then after implementing your custom view however you want, just set the frame of your custom view to self.tabBar.frame in viewDidLayoutSubviews.
For changing viewControllers, call this when the user taps one of your custom tabs:
self.selectedIndex = newIndex

How can I animate a gradient layer which increases its width?

I'm using a simple UIView which I want to animate and I added a gradient layer to it.
I want to increase the width of the view and the layer placed on the view,
but all I get is that the view increases its width but not the layer.
Example: Let's say I have a UIView with height = width = 50
I animate it by setting the width to: width += 50. This animation is working. If I do the same with layer then noting happens. The layer does not increase its width. I tried some things to fix this (see comments in code) but nothing is working.
Here is my code
func performNextTitleAnimation() {
let overlayViewHeight = overlayView.frame.size.height
let overlayViewWidth = overlayView.frame.size.width
let animationHeight: CGFloat = 48
let overlayViewHalfHeight = (overlayViewHeight) / 2
swipeAnimation = UIView(frame: CGRect(x: 0, y: overlayViewHalfHeight - (animationHeight/2), width: 48, height: animationHeight))
swipeAnimation.backgroundColor = .gray
swipeAnimation.layer.cornerRadius = swipeAnimation.frame.size.height / 2
gradientLayer.frame = swipeAnimation.bounds
overlayView.addSubview(swipeAnimation)
swipeAnimation.layer.addSublayer(gradientLayer)
UIView.animate(withDuration: 1.2, delay: 0, options: [.repeat], animations: {
self.gradientLayer.cornerRadius = 24
self.swipeAnimation.frame.size.width += 50
//Things I tried, but not working
//1.) self.gradientLayer.frame.size.width += 50
//2.) self.gradientLayer.frame.size.width = self.swipeAnimation.frame.size.width
//3.) self.gradientLayer.bounds = self.swipeAnimation.bounds
}, completion: nil)
}
Gradient Layer
gradientLayer.colors = [UIColor.white.cgColor, animationColor.cgColor]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.transform = CATransform3DMakeRotation(3*(CGFloat.pi) / 2, 0, 0, 1)
Any help is highly appreciated.
The best result I've ever achieved when I needed to animate CAGrandientLayers size is to use it as a layerClass of a custom UIView:
class GrandientView: UIView {
override class var layerClass : AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
// it is safe to force cast here
// since we told UIView to use this exact type
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
// setup your gradient
}
}

How to update NSView layer position and anchor point for transform simultaneously with NSView frame for mouse events

I am currently trying to implement some custom NSViews that will be animate by certain events in a macOS app. The controls are along the lines of slider you would find in an AudioUnit Plugin.
The question of animation has been answered in this question.
What I am uncertain of is how to alter a view's CALayer position and anchor point for the rotation and updating its frame for mouse events.
As a basic example, I wish to draw a square by creating an NSView and setting it's background colour. I then want to animate the rotation of the view, a la the previous link, on a mouseDown event.
With following setup, the view is moved to the centre of window, its anchor point is altered so that the view rotates around it and not around the origin of the window.contentView!. However, moving the view.layer.position and setting the view.layer!.anchorPoint = CGPoint(x: 0.5,y: 0.5) does not also move the frame that will detect events.
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let view = customView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
window.contentView!.wantsLayer = true
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
window.contentView?.addSubview(view)
let containerFrame = window.contentView!.frame
let center = CGPoint(x: containerFrame.midX, y: containerFrame.midY)
view.layer?.position = center
view.layer?.anchorPoint = CGPoint(x: 0.5,y: 0.5)
}
The customView in this case is simply an NSView with the rotation animation from the previous link executed on mouseDown
override func mouseDown(with event: NSEvent)
{
let timeToRotate = 1
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0.0
rotateAnimation.toValue = angle
rotateAnimation.duration = timeToRotate
rotateAnimation.repeatCount = .infinity
self.layer?.add(rotateAnimation, forKey: nil)
}
Moving the frame by setting view.frame = view.layer.frame results in the view rotating around one of its corners. So, instead I have altered the view.setFrameOrigin()
#IBOutlet weak var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification)
{
let view = customView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
window.contentView!.wantsLayer = true
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blue.cgColor
window.contentView?.addSubview(view)
let containerFrame = window.contentView!.frame
let center = CGPoint(x: containerFrame.midX, y: containerFrame.midY)
let offsetFrameOrigin = CGPoint(x: center.x - view.bounds.width/2,
y: center.y - view.bounds.height/2)
view.setFrameOrigin(offsetFrameOrigin)
view.layer?.position = center
view.layer?.anchorPoint = CGPoint(x: 0.5,y: 0.5)
}
Setting the view.frame origin to an offset of the centre achieves what I want, but I cannot help but feel this is a little 'hacky' and that I may be approaching this the wrong way. Especially since any further change to view.layer or view.frame will result in either the animation being incorrect or events being detected outside what is drawn.
How can I alter NSView.layer so that it rotates around its centre at the same time as setting the NSView.frame so that mouse events are detected in the correct area?
Also, is altering NSView.layer.anchorPoint a correct way to set it up for rotation around its centre?
I think I'm in much the same position as you. Fortunately, I stumbled across a gist that extends NSView with a setAnchorPoint function that does the job for me (keeping the layer's anchor in sync with the main frame).
There's a fork for that gist for Swift 4. Here's the code itself:
extension NSView
{
func setAnchorPoint (anchorPoint:CGPoint)
{
if let layer = self.layer
{
var newPoint = CGPoint(x: self.bounds.size.width * anchorPoint.x, y: self.bounds.size.height * anchorPoint.y)
var oldPoint = CGPoint(x: self.bounds.size.width * layer.anchorPoint.x, y: self.bounds.size.height * layer.anchorPoint.y)
newPoint = newPoint.applying(layer.affineTransform())
oldPoint = oldPoint.applying(layer.affineTransform())
var position = layer.position
position.x -= oldPoint.x
position.x += newPoint.x
position.y -= oldPoint.y
position.y += newPoint.y
layer.position = position
layer.anchorPoint = anchorPoint
}
}
}
You'd then use it like this on the NSView directly (i.e. not its layer):
func applicationDidFinishLaunching(_ aNotification: Notification)
{
// ... //
let view = customView(frame: NSRect(x: 0, y: 0, width: 50, height: 50))
view.wantsLayer = true
view.setAnchorPoint(anchorPoint: CGPoint(x: 0.5, y: 0.5))
// ... //
}