Live Interface Builder update with #IBDesignable for custom NSSlider control - swift

I have problems in getting my custom NSSlider control updated live within Xcode's Interface Builder.
I have implemented #IBDesignable and prepareForInterfaceBuilder as shown in many other posts and tutorials. My little test just removes the knob from the slider control.
Here is the code I am using at the moment:
import Cocoa
#IBDesignable
class ColorSlider2: NSSlider {
override func setNeedsDisplay(_ invalidRect: NSRect) {
super.setNeedsDisplay(invalidRect)
}
override func awakeFromNib() {
super.awakeFromNib()
setupView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
private func setupView() {
if ((self.cell?.isKind(of: ColorSlider2Cell.self)) == false) {
let cell = ColorSlider2Cell()
self.cell = cell
}
self.alphaValue = 0.5
self.floatValue = 0.4
}
}
class ColorSlider2Cell: NSSliderCell {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init() {
super.init()
}
override func drawKnob(_ knobRect: NSRect) {
return
}
}
The preview in Interface Builder is neither removing the knob nor updating the floatValue:
Do you have any idea why this is the case?

Related

How to create a custom UILabel in Swift with a specific color

I am trying to create a custom UILabel where the text color would be red.
Here's what I tried and none of this works:
class CustomLabel: UILabel {
override func awakeFromNib() {
UILabel.appearance().textColor = UIColor.blue
textColor = UIColor.blue
}
}
You're missing another case, a Label can be created with and without nib. Try this:
class MyCustomLabel: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
// This will call `awakeFromNib` in your code
setup()
}
private func setup() {
self.textColor = .red
}
}

No way to pass new argument to `init` for `NSView`

In the following code, I'm trying to pass an additional argument to VidePlayerView. Right now, I'm creating an instance of VideoPlayerView and I'm passing no arguments to it. However, this ends up calling the init methods with an NSRect arguments (which I don't really know where that comes from).
I want to pass an additional argument to VideoPlayerView, but I have no idea how to do this since it seems that I don't have access to the frame argument.
import SwiftUI
import AVKit
// Note: I couldn't find a way to pass this through the `init` method
var playerLayer = AVCaptureVideoPreviewLayer()
final class VideoPlayerView: NSView {
// MARK: - Initializers
override public init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
required public init?(coder decoder: NSCoder) {
super.init(coder: decoder)
commonInit()
}
private func commonInit() {
// Do something with playerLayer
}
}
public struct VideoPlayerViewView: NSViewRepresentable {
init(layer: AVCaptureVideoPreviewLayer?) {
if let layer = layer {
playerLayer = layer
} else {
print("No Layer Set")
}
}
public func makeNSView(context: NSViewRepresentableContext<VideoPlayerViewView>) -> NSView {
return VideoPlayerView()
}
}
Currently, I'm solving this problem by ignoring the frame argument, but not sure if that is important.
final class VideoPlayerView: NSView {
private var playerLayer = AVCaptureVideoPreviewLayer()
private let rootLayer = CALayer()
// MARK: - Initializers
override public init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
public init(layer: AVCaptureVideoPreviewLayer) {
self.playerLayer = layer
super.init(frame: NSRect())
commonInit()
}
...
public func makeNSView(context: NSViewRepresentableContext<VideoPlayerViewView>) -> NSView {
return VideoPlayerView(layer: self.playerLayer)
}
Here is possible approach. Tested with Xcode 11.4
final class VideoPlayerView: NSView {
private var playerLayer: AVCaptureVideoPreviewLayer?
// MARK: - Initializers
override public init(frame frameRect: NSRect) {
super.init(frame: frameRect)
commonInit()
}
public convenience init(frame frameRect: NSRect = .zero, layer: AVCaptureVideoPreviewLayer?) {
self.init(frame: frameRect)
self.playerLayer = layer
}
required public init?(coder decoder: NSCoder) {
super.init(coder: decoder)
commonInit()
}
private func commonInit() {
// Do something with playerLayer
}
}
public struct VideoPlayerViewView: NSViewRepresentable {
init(layer: AVCaptureVideoPreviewLayer?) {
if let layer = layer {
playerLayer = layer
} else {
print("No Layer Set")
}
}
public func makeNSView(context: NSViewRepresentableContext<VideoPlayerViewView>) -> NSView {
return VideoPlayerView(layer: playerLayer)
}
public func updateNSView(_ nsView: NSView, context: Context) {
}
}

extension for UITextfield keyboard appearance

in my app, I will add different themes, but for now, I want to write an extension that can handle keyboard appearance from extension.
for the first step, I just want to have control of its appearance, so I set that it should be .dark but it's not working. could you tell me where is the problem in this simple extension?
I just want it to change the keyboard appearance automatically without doing anything else
extension UITextView {
var keyboardApperance: UIKeyboardAppearance? {
get {
return self.keyboardAppearance
}
set {
self.keyboardAppearance = .dark
}
}
}
Creating just an extension doesn't change your UITextView properties.
You can create your own custom UITextView and use it instead of UITextView.
Don't forget to set your class if you are using storyboard or xib.
#IBDesignable
public class CustomDarkTextView: UITextView {
public override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setup()
}
public convenience init(frame: CGRect) {
self.init(frame: frame, textContainer: nil)
setup()
}
public convenience init() {
self.init(frame: CGRect.zero, textContainer: nil)
setup()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
self.keyboardAppearance = UIKeyboardAppearance.Dark
}
}
trying using functions like this
extension UITextView {
func setKeyboardToDark() {
self.keyboardAppearance = .dark
}
func setKeyboardToLight() {
self.keyboardAppearance = .light
}
}
extension UITextView {
func setDarkKeyboard() {
self.keyboardAppearance = UIKeyboardAppearance.dark
}
func setLightKeyboard() {
self.keyboardAppearance = UIKeyboardAppearance.light
}
}

How To Create A Re-usable IBDesignable Code

When using IBDesignable, the following code is common and I end up repeating every time I create a class, is there a way to avoid this repetition?
override init(frame: CGRect) {
super.init(frame: frame)
themeProp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeProp()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
themeProp()
}
This is how I am currently using IBDesignable to create styles for UIButton.
import UIKit
let colorWhite = colorLiteral(red: 0.9999127984, green: 1, blue: 0.9998814464, alpha: 1)
let colorLavender = colorLiteral(red: 0.6604440808, green: 0.5388858914, blue: 0.8827161193, alpha: 1)
#IBDesignable class PrimaryButtonA: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
themeProp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeProp()
}
func themeProp() {
setTitleColor(colorWhite, for:.normal)
self.layer.cornerRadius = 10
backgroundColor = colorLavender
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
themeProp()
}
}
#IBDesignable class PrimaryButtonB: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
themeProp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeProp()
}
func themeProp() {
setTitleColor(colorWhite, for:.normal)
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
themeProp()
}
}
With my limited knowledge, I tried to create a function and tried calling it inside each class but it doesn't work.
It doesn't make any sense to repeat this 12 lines of code in every class declaration. So, if there is a way to avoid this repetition then please use my code for the answer.
Thanks!
A possible solution is creating a common superclass for these views. The only disadvantage is that you have to create a superclass for each type (UIViews, UIButtons etc.)
class DesignableView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
themeProp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
themeProp()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
themeProp()
}
func themeProp() { }
}
After that, make your designable class a subclass of DesignableView. You will only have to override themeProp() in that.

Is there a way to make an Xcode template that generates a View-Controller pair?

I often use this pattern when creating UIViewController/UIView pairs. It would be nice if I could define a template in Xcode so that I could click New File -> [template] and generate a MyViewController.swift and MyView.swift like in the example below.
MyViewController.swift
class MyViewController: UIViewController {
override func loadView() {
self.view = MyView()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
extension MyViewController : MyViewDelegate
{
// Provide data, pop off a navigation stack, etc
}
MyView.swift
protocol MyViewDelegate : class {
}
class MyView: UIView
{
weak var delegate : MyViewDelegate?
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup()
{
// Configure views
// Assemble
}
}