Mark Cornelisse

  • Home
  • Apps
    • We All Pay
      • We all pay for iPhone
      • We all pay for iPad
    • Freeze – Your cool drink buddy
    • Help me decide between for iOS
  • Mac Apps
    • EMC – Easy Money Converter
    • Help Me Decide Between
  • Blog
  • About

Category Archives: AppKit

Cocoa: A circular NSView with a CALayer

Posted on May 11, 2015 by Mark

For a couple of days I’ve been looking into creating a NSView, that simply shows a NSImage shaped in a circle. I could have done this in NSView’s drawRect method and be done with it. But I also want to expand my knowledge to a better knowledge of the AppKit framework. So stubborn as I am I decided to do this with CALayer like the iOS example I had. This to make it more smoothly animated and to learn some basics about Core Animation.
What I didn’t know before hand is that NSView has three different modes in which it handles it’s content. Compared to UIView from the Cocoa Touch Frameworks, which already has a CALayer ready to use.
Here are the different modes of an instance of the NSView Class:

  1. Classic view: DrawRect:dirtyRect is called to draw the screen. There is one draw back in this. All drawing takes place in the main thread. Which if performance is getting an issue can become an issue.
  2. Layer Backed view: The NSView now has a CALayer which is managed by the NSView. This actually takes up more memory space, but since the NSView knows of every CALayer that is underneath it. It’s system managed. View updates are called through the updatesLayer() method. The layers are drawn multithreaded by OS X.
  3. Layer Hosted view: In this mode the NSView has no knowledge about the CALayer or any of it’s sublayers it contains and it won’t care about it either. As a programmer you have to manage it. The NSView just shows it, but it will use drawRect:dirtyRect to draw the CALayer.

Nothing says as much as a bit of code. Here’s the code for creating a circular shaped NSView that actually shows an NSImage on screen. I have to remind you that I also wrote a quartzPath methode for NSBezierPath, since the Cocoa version doesn’t have a CGPath method that returns a CGPath of the NSBezierPath.

Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import Foundation
import AppKit
import MCTools
 
 
@IBDesignable public class CircularImageView: NSView {
    // This class provides a circular image view.
 
    // MARK: New In This Class
    var imageLayer: CALayer?
 
    public var image: NSImage? {
        didSet {
            if let newImage = image {
                newImage.lockFocus()
                imageLayer?.contents = newImage
                newImage.unlockFocus()
            }
        }
    }
 
    private func prepareLayer() {
        self.wantsLayer = true
    }
 
    private func drawImageInLayer() {
        let insetBounds = CGRectInset(bounds, edgeInset, edgeInset)
        var circularMask: CALayer {
            var circularImageMask = CAShapeLayer()
            self.layer!.addSublayer(circularImageMask)
            let circularMaskPath = NSBezierPath(ovalInRect: insetBounds)
            circularImageMask.path = circularMaskPath.quartzPath().takeRetainedValue()
            return circularImageMask
        }
 
        func createNewImageLayer() -> CALayer {
            let newImageLayer = CALayer()
            newImageLayer.frame = insetBounds
            if let imageToSet = image {
                imageToSet.lockFocus()
                newImageLayer.contents = imageToSet
                newImageLayer.contentsGravity = kCAGravityResizeAspectFill
                imageToSet.unlockFocus()
            }
            return newImageLayer
        }
 
        if imageLayer == nil {
            imageLayer = createNewImageLayer()
            self.layer!.mask = circularMask
            self.layer!.addSublayer(imageLayer)
        }
        if let imageToSet = image {
            imageToSet.lockFocus()
            imageLayer!.contents = imageToSet
            imageLayer!.contentsGravity = kCAGravityResizeAspectFill
            imageToSet.unlockFocus()
        }
 
    }
 
    // MARK: NSView stuff
 
    public override init(frame frameRect: NSRect) {
        super.init(frame: frameRect)
        prepareLayer()
    }
 
    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        prepareLayer()
    }
 
    public override func updateLayer() {
        super.updateLayer()
 
        drawImageInLayer()
    }
 
    // MARK: Interface Builder Stuff
 
    @IBInspectable public var edgeInset: CGFloat = 10
 
    public override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        // Load default test image.
        println("\(self): prepareForInterfaceBuilder")
        let processInfo = NSProcessInfo.processInfo()
        let environment = processInfo.environment
        let projectSourceDirectories : AnyObject = environment["IB_PROJECT_SOURCE_DIRECTORIES"]!
        let directories = projectSourceDirectories.componentsSeparatedByString(":")
 
        if directories.count != 0 {
            let firstPath = directories[0] as! String
            let imagePath = firstPath.stringByAppendingPathComponent("CircularView/Bz1dSvR.jpg")
 
            let image = NSImage(contentsOfFile: imagePath)
            image!.setName("Test Image")
            self.image = image
        }
    }
}

I hope this is somehow useful to you. Please feel free to use it anyway you like.

Posted in AppKit, cocoa | Tags: CALayer, NSBezierPath, NSImage, NSView | 2 Comments |

Search

Pages

  • Apps
    • Freeze – Your cool drink buddy
    • Help me decide between for iOS
    • We All Pay
      • We all pay for iPhone
      • We all pay for iPad
  • Home
  • Mac Apps
    • EMC – Easy Money Converter
    • Help Me Decide Between
  • Blog
  • About
    • Cookies

Archives

  • July 2016
  • May 2015
  • March 2015
  • February 2015
  • January 2015
  • April 2014

Categories

  • AppKit (1)
  • Blog (3)
    • Core Data (1)
    • iOS (1)
  • CloudKit (1)
  • cocoa (3)
  • Launch Screen (1)
  • LLVM (1)
  • Objective-C (1)
  • osx (2)
  • swift (3)
  • xcode (2)

WordPress

  • Log in
  • WordPress
Copyright 2016 © Mark Cornelisse
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish.Accept Read More
Privacy & Cookies Policy