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

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) {
}
}

Related

Correct calling init function in Apple Swift 5 subclass in Cocoa application

I cannot figure out the correct format for calling the init function of NSTextField in a subclass called HyperlinkTextField.
class HyperlinkTextField: NSTextField {
var url: String = ""
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared.open(URL(string: url)!)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
isBezeled = false
}
init(string: String) {
super.init(string: string) // error: must call a designated initializer of the superclass 'NSTextField'
isBezeled = false
}
}
All classes inheriting from NSControl have two designated initializers
init?(coder: NSCoder)
init(frame: NSRect)
So you must call the latter
init(string: String) {
super.init(frame: .zero)
self.stringValue = string
isBezeled = false
}

How do I initialize variables using self.frame in init(size:)

My class require an init(size:) constructor for initialization and need to call super.init(size:size) after my variables. This is problematic since I want to initialize my variables with size. If I try this:
override init(size: CGSize) {
tileWidth = self.frame.width/8
tileHeight = tileWidth/2
super.init(size: size)
}
...then I get an error saying
'self' used in property access 'frame' before 'super.init' call
and if I do this:
override init(size: CGSize) {
super.init(size: size)
tileWidth = self.frame.width/8 //140
tileHeight = tileWidth/2
}
...I get an error saying
Property 'self.tileHeight' not initialized at super.init call
So how do I make this happen?
EDIT
In its context the code looks like this:
class TileBoard: SKScene {
let tileWidth:CGFloat
let tileHeight:CGFloat
override init(size: CGSize) {
super.init(size: size)
tileWidth = self.frame.width/8
tileHeight = tileWidth/2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Consider
class TopView:UIView {
var tileWidth:CGFloat?
var tileHeight:CGFloat?
init(size: CGSize) {
super.init(frame: CGRect.init(x: 0, y: 0, width: size.width, height: size.height))
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class BottomView:TopView {
override init(size: CGSize) {
super.init(size: size)
tileWidth = frame.width / 8.0
tileHeight = tileWidth / 2.0
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I fixed it using factory based dependency injection like this:
protocol DependencyInjectionProtocol {
func injection() -> CGFloat
}
class InjectWidth: UIViewController, DependencyInjectionProtocol {
func injection() -> CGFloat {
return self.view.frame.width/8.0
}
}
class InjectHeight: UIViewController, DependencyInjectionProtocol {
func injection() -> CGFloat {
let height = (self.view.frame.width/8.0) / 2
return height
}
}
class InjectionClient {
let delegate: DependencyInjectionProtocol
init(delegate: DependencyInjectionProtocol) {
self.delegate = delegate
}
}
class TileBoard: SKScene {
let tileWidth:CGFloat = InjectionClient(delegate: InjectWidth()).delegate.injection()
let tileHeight:CGFloat = InjectionClient(delegate: InjectHeight()).delegate.injection()
...

Two classes to use the same function to create custom view - Swift

I am kind of new to Swift and I can't figure this out. I have two classes where I need to use the same function to set up a custom UIStackVIew (Rating Control that shows rating stars). Each class has a variable called value that needs to be passed inside the function. I don't want to be duplicating the same code of the setUpStackView function inside each class. I have the following code:
class Class1: UIStackView {
var variable1 = "value1"
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
setUpStackView(value: variable1)
}
class Class2: UIStackView {
var variable2 = "value2"
override init(frame: CGRect){
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
setUpStackView(value: variable2)
}
extension Class1 {
func setUpStackView(value: String){
//code to set UIStackView rating control and to use the variable value
}
}
How can I implement the extension for the Class2? I am stuck with this. Any help would be greatly appreciated!
One solution can be moved common code to protocol where you can abstract out:
protocol BaseStackView {
var variable :String { get set }
}
class Class1: UIStackView,BaseStackView {
var variable = "value1"
override init(frame: CGRect){
super.init(frame: frame)
self.setUpStackView(value: variable)
}
required init(coder: NSCoder) {
super.init(coder: coder)
self.setUpStackView(value: variable)
}
}
class Class2: UIStackView,BaseStackView {
var variable = "value2"
override init(frame: CGRect){
super.init(frame: frame)
self.setUpStackView(value: variable)
}
required init(coder: NSCoder) {
super.init(coder: coder)
self.setUpStackView(value: variable)
}
}
extension UIStackView {
func setUpStackView(value: String) {
//Your setup here
}
}
You have many options.
You can make Class2 inherit from Class1:
class Class2: Class1 {
var value = "value2" //You have access to Class1's value, so you can change it here
setUpStackView(value: value) //But there's a problem here
}
But you can't just call a function when you're in the middle of a class declaration.
But you can do this in the initializer:
class Class2: Class1 {
override init(frame: CGRect) {
super.init(frame: frame)
value = "value2"
setUpStackView(value: value)
}
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
value = "value2"
setUpStackView(value: value)
}
}
The above is kind of jank because you're referencing class properties within its own initializer.
Here's another solution:
You can make your setup function a function extension of UIStackView:
extension UIStackView {
func setUpStackView(value: String) {
//Your setup here
self.someProperty = value //self is referring to the stackview itself
}
}
Another option is to create a static function.
extension Class1 {
static func setUpStackView(stackVw: UIStackView, value: String) {
stackVw.someProperty = value
//Doing it like this still makes this function "belong" to Class1
//It also makes it so anyone can set up their stack view like
//this because they have to pass their stack view in here
}
}
Usage would be like this regardless of what class you're in and what inheritance hierarchy you have:
override init(frame: CGRect) {
super.init(frame: frame)
Class1.setUpStackVw(stackVw: self, value: self.variable)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Class1.setUpStackVw(stackVw: self, value: self.variable)
}
Yet another option is to create a protocol from which both Class1 and Class2 conform to.
protocol StackVwWithSpecialSetUp where Self: UIStackView {
var value: String {get}
}
extension StackVwWithSpecialSetUp {
func setUpStackView()
{
self.someProperty = self.value
}
}
And then you'd have your class conform to it
class Class1Or2: UIStackView, StackVwWithSpecialSetUp {
var value: String = "blah" //Compiler will force you to implement this
override init(frame: CGRect) {
super.init(frame: frame)
//Now you can use this
setUpStackVw()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//You can use this here too
setUpStackVw()
}
}
Perhaps the way I'd do it is to just eliminate the need for the value property altogether:
class Class1: UIStackView {
override init(frame: CGRect) {
super.init(frame: frame)
setUpStackVw()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpStackVw()
}
func setUpStackVw()
{
self.accessibilityHint = "value1"
}
}
//////////
class Class2: Class1 {
override func setUpStackVw()
{
self.someProperty = "value2"
}
}

Live Interface Builder update with #IBDesignable for custom NSSlider control

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?

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
}
}