IBInspectable not rendering Layer of NSView - macos

I want to edit border width and background color of an NSView and made those values IBInspectable:
#IBInspectable var borderWidth: CGFloat{
set{
layer?.borderWidth = newValue
}
get{
return layer!.borderWidth
}
}
#IBInspectable var backgroundColor: NSColor{
set{
layer?.backgroundColor = newValue.CGColor
}
get{
return NSColor(CGColor: layer!.backgroundColor)!
}
}
In the init method, I wrote:
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
wantsLayer = true
layer?.setNeedsDisplay()
}
When I run the app, changes are shown correctly, but the view doesn't live-render in the interface builder. I don't get any errors or warnings either.

i found the solution myself: it seems that the init Method does not get called when the view gets rendered in the Interface builder. As a solution i had to add a global variable which creates a CALayer() when needed:
#IBDesignable class CDPProgressIndicator: NSView {
// creates a CALayer() and sets it to the layer property of the view
var mainLayer: CALayer{
get{
if layer == nil{
layer = CALayer()
}
return layer!
}
}
//referencing to mainLayer ensures, that the CALayer is not nil
#IBInspectable var borderWidth: CGFloat{
set{
mainLayer.borderWidth = newValue
}
get{
return mainLayer.borderWidth
}
}
#IBInspectable var backgroundColor: NSColor{
set{
mainLayer.backgroundColor = newValue.CGColor
}
get{
return NSColor(CGColor: mainLayer.backgroundColor)!
}
}
and now it finally renders in the interface builder as well.

The setting of wantsLayer is sufficient to have the layer created when running the app, but it doesn’t work in IB. You have to set the layer explicitly. I’d suggest doing this in prepareForInterfaceBuilder, that way you are using the approved technique, wantsLayer, in your app, but we handle the IB exception, too. For example:
#IBDesignable
class CustomView: NSView {
#IBInspectable var borderWidth: CGFloat {
didSet { layer!.borderWidth = borderWidth }
}
#IBInspectable var borderColor: NSColor {
didSet { layer!.borderColor = borderColor.cgColor }
}
#IBInspectable var backgroundColor: NSColor {
didSet { layer!.backgroundColor = backgroundColor.cgColor }
}
// needed because IB doesn't don't honor `wantsLayer`
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer = CALayer()
configure()
}
override init(frame: NSRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
configure()
}
private func configure() {
wantsLayer = true
...
}
}
That handles this IB edge case (bug?), but doesn’t alter the standard flow when running the app.

Related

Xcode: How do I reset the state of all Buttons from the main view controller using a class

I have a class for my buttons that changes the colour when pressed, alternating between on and off.
class KSPickButton: UIButton {
var isOn = true
override init(frame: CGRect) {
super.init(frame: frame)
initButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initButton()
}
func initButton() {
layer.borderWidth = 2.0
layer.borderColor = Colors.shanklinGreen.cgColor
layer.cornerRadius = frame.size.height/2
backgroundColor = .clear
setTitleColor(.white, for: .normal)
addTarget(self, action: #selector(KSPickButton.buttonPressed), for: .touchUpInside)
}
#objc func buttonPressed() {
activateButton(bool: !isOn)
}
func activateButton(bool: Bool) {
isOn = bool
let color = bool ? .clear : Colors.shanklinGreen
//let title = bool ? "" : ""
let titleColor = bool ? .white: Colors.shanklinBlack
//setTitle(title, for: .normal)
setTitleColor(titleColor, for: .normal)
backgroundColor = color
}
}
This works perfectly. I have 20 buttons on my main view controller and they flip between on and off as expected... Then maybe after pressing 6, I want to reset them all to off. I have a reset button on my main view controller, but I cannot work out how I can reset them all?
I can make them all look reset but the bool remains as was...
How do I call this class for all buttons and reset them correctly?
Introduced an observer with 'isOn' variable. You can try using following code snippet in 'KSPickButton' class. After that you need to take all subviews from view controllers (where buttons are placed) and set 'isOn' to 'false' for all of them.
class KSPickButton: UIButton {
public var isOn:Bool = true {
didSet {
handleButtonStateChange()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initButton()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initButton()
}
func initButton() {
layer.borderWidth = 2.0
layer.borderColor = Colors.shanklinGreen.cgColor
layer.cornerRadius = frame.size.height/2
backgroundColor = .clear
setTitleColor(.white, for: .normal)
addTarget(self, action: #selector(KSPickButton.buttonPressed), for: .touchUpInside)
}
#objc func buttonPressed() {
isOn = !isOn
}
func handleButtonStateChange() {
let color = isOn ? .clear : Colors.shanklinGreen
let titleColor = isOn ? .white: Colors.shanklinBlack
setTitleColor(titleColor, for: .normal)
backgroundColor = color
}
}
Here is code snippet you need to implement in your view controller as explained above.
class MyVC: UIViewController {
------
------
for subview in view.subviews where subview.isKind(of: KSPickButton.self)&&(subview as? KSPickButton)?.isOn == true {
(subview as? KSPickButton)?.isOn = false
}
-----
-----
}

macOS: Cannot center NSTextField inside custom NSView based class

I am creating a custom NSView based class with this code.
import Foundation
import AppKit
#IBDesignable
class MacBotaoMudaDeCor : NSView {
lazy var etiqueta: NSTextField = {
let etiquetax = NSTextField()
etiquetax.textColor = corLigada
let xConstraint = NSLayoutConstraint(item: etiquetax,
attribute: .centerX,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1,
constant: 0)
let yConstraint = NSLayoutConstraint(item: etiquetax,
attribute: .centerY,
relatedBy: .equal,
toItem: self,
attribute: .centerX,
multiplier: 1,
constant: 0)
self.addConstraint(xConstraint)
self.addConstraint(yConstraint)
etiquetax.integerValue = self.numeroBotao
etiquetax.sizeToFit()
self.addSubview(etiquetax)
return etiquetax
}()
// creates a CALayer() and sets it to the layer property of the view
var mainLayer: CALayer{
get{
if layer == nil{
layer = CALayer()
}
return layer!
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
mainLayer.cornerRadius = cornerRadius
mainLayer.masksToBounds = cornerRadius > 0
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
mainLayer.borderWidth = borderWidth
}
}
#IBInspectable var borderColor: NSColor? {
didSet {
mainLayer.borderColor = borderColor?.cgColor
}
}
#IBInspectable var corDesligada: NSColor = NSColor.white
#IBInspectable var corLigada: NSColor = NSColor.black
#IBInspectable var ligado: Bool = false {
didSet {
self.ajustar(corFundo: ligado ? self.corLigada : self.corDesligada)
}
}
#IBInspectable var numeroBotao: Int = 0 {
didSet {
etiqueta.integerValue = numeroBotao
}
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
override func awakeFromNib() {
self.ajustar(corFundo: corDesligada)
self.ajustar(corEtiqueta: corLigada)
}
func ajustar(corFundo:NSColor) {
mainLayer.backgroundColor = corFundo.cgColor
}
func ajustar(corEtiqueta:NSColor) {
etiqueta.textColor = corLigada
}
}
The idea is to have a NSTextField centered on the view that this class represents.
Xcode crashes trying to install the constraints with this message.
2019-06-26 06:08:42.341770+0100 Failed to set (contentViewController)
user defined inspected property on (NSWindow): Constraint improperly
relates anchors of incompatible types:
addConstraint(_:):
The constraint must involve only views that are within scope of the receiving view.
Add the view before adding the constraints.

NSCollectionView single selection is not working, but multiple selection is fine

As title described,
my NSCollectionView is not working when single selection.
MyNSCollectionView is rendered correcttly,
below code shows how I initialize my NSCollectionView:
self.leftBar.dataSource = self
self.leftBar.delegate = self
self.leftBar.isSelectable = true
self.leftBar.allowsEmptySelection = true
let layout = NSCollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = NSSize(width: 200 , height: 50)
leftBar.collectionViewLayout = layout
self.contentView.addSubview(leftBar)
leftBar <- [
Top(),
Left(),
Bottom(),
Width(200)
]
Custom NSCollectionViewItem
class LeftBarCell: NSCollectionViewItem {
var leftBarView : LeftBarView?
override func loadView() {
leftBarView = LeftBarView(frame: NSZeroRect)
view = leftBarView!
}
func setup(title : String){
leftBarView?.titleTextView.string = title
}
}
In LeftBarView
class LeftBarView: NSView {
lazy var titleTextView : NSTextView = {
let titleTextView = NSTextView()
titleTextView.isEditable = false
titleTextView.isSelectable = false
titleTextView.font = NSFont(name: "Helvetica", size: 20)
return titleTextView
}()
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.setupViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setupViews(){
self.addSubview(titleTextView)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.lightGray.cgColor
titleTextView <- Edges()
}
}
I tried to play around with
self.leftBar.isSelectable = true
leftBar.allowsMultipleSelection = true
and multiple selection is working.
The didSelectItemsAt from NSCollectionViewDelegate is triggered
But then when I try with this
self.leftBar.isSelectable = true
or
self.leftBar.isSelectable = true
self.leftBar.allowsEmptySelection = true
It is not working when I click on the collectionviewitem,
The didSelectItemsAt from NSCollectionViewDelegate is not triggered.
Any thoughts would be appreciated, thanks!
Solved. This is because the textview covers the cell, u might need to disable the textview to become passive.
extension NSTextView {
open override func hitTest(_ point: NSPoint) -> NSView? {
return nil
}
}

Swift - GLKit View CIFilter Image

I am trying to use a GLIKit View in order to modify an Image. The class I have so far is working well all the CIFilters except for the CILineOverlay it renders a black view. If I use any other effect it works well.
Why is the CILineOverlay not showing?
class ImageView: GLKView {
let clampFilter = CIFilter(name: "CIAffineClamp")!
let blurFilter = CIFilter(name: "CILineOverlay")!
let ciContext:CIContext
override init(frame: CGRect) {
let glContext = EAGLContext(API: .OpenGLES2)
ciContext = CIContext(
EAGLContext: glContext,
options: [
kCIContextWorkingColorSpace: NSNull()
]
)
super.init(frame: frame, context: glContext)
enableSetNeedsDisplay = true
}
required init(coder aDecoder: NSCoder) {
let glContext = EAGLContext(API: .OpenGLES2)
ciContext = CIContext(
EAGLContext: glContext,
options: [
kCIContextWorkingColorSpace: NSNull()
]
)
super.init(coder: aDecoder)!
context = glContext
enableSetNeedsDisplay = true
}
#IBInspectable var inputImage: UIImage? {
didSet {
inputCIImage = inputImage.map { CIImage(image: $0)! }
}
}
#IBInspectable var blurRadius: Float = 0 {
didSet {
//blurFilter.setValue(blurRadius, forKey: "inputIntensity")
setNeedsDisplay()
}
}
var inputCIImage: CIImage? {
didSet { setNeedsDisplay() }
}
override func drawRect(rect: CGRect) {
if let inputCIImage = inputCIImage {
clampFilter.setValue(inputCIImage, forKey: kCIInputImageKey)
blurFilter.setValue(clampFilter.outputImage!, forKey: kCIInputImageKey)
let rect = CGRect(x: 0, y: 0, width: drawableWidth, height: drawableHeight)
ciContext.drawImage(blurFilter.outputImage!, inRect: rect, fromRect: inputCIImage.extent)
}
}
}
The Apple docs states "The portions of the image that are not outlined are transparent." - this means you are drawing black lines over a black background. You can simply composite the output from the filter over a white background to make the lines appear:
let background = CIImage(color: CIColor(color: UIColor.whiteColor()))
.imageByCroppingToRect(inputCIImage.extent)
let finalImage = filter.outputImage!
.imageByCompositingOverImage(background)

Changing background color of NSView in Swift 2.0

I am trying to change the background color of NSView and have tried one of the solution outlined in this answer. However, it's not working for me. What am I missing here? Should I use viewWillAppear() here or it only meant for iOS?
Related code:
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
override func awakeFromNib() {
if self.view.layer != nil {
let color : CGColorRef = CGColorCreateGenericRGB(1.0, 0, 0, 1.0)
self.view.layer?.backgroundColor = color
}
}
}
P.S - I'm new to OS X/iOS programming and trying to figure my way out through documentation and existing solutions. Using Xcode 7 beta 4.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.wantsLayer = true
view.layer?.backgroundColor = NSColor(red: 1, green: 0, blue: 0, alpha: 1).CGColor
}
override var representedObject: AnyObject? {
didSet {
}
}
}

Resources