I'm new to swift and Cocoa dev my problem is:-
NSView's draw() function is being called twice. the results and code are below :-
results
[3/3] Linking MyCanvasApp
Hello, World
You got nothing.
You got nothing.
^C
as you can see "You got nothing" is being printed twice
PS everything is handwritten I'm not using Xcode at all
main.swift
import Cocoa
var app = NSApplication.shared
app.setActivationPolicy(.regular)
let delegate = AppDelegate()
app.delegate = delegate
app.activate(ignoringOtherApps: true)
app.run()
AppDelegate.swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
print("Hello, World")
let frame = NSRect.init(x: 100.0, y: 100.0, width: 300.0, height: 300.0)
let win = NSWindow.init(contentRect: frame, styleMask: [.closable, .titled], backing: .buffered, defer: false)
win.title = "My Canvas App"
win.makeKeyAndOrderFront(nil)
let frame2 = NSRectToCGRect(NSRect.init(x: 0, y: 0, width: 300.0, height: 300.0))
let canvas = CanvasView.init(frame: frame2)
win.contentView!.addSubview(canvas)
}
}
does NSView.init (CanvasView.init) calls it or something? ^^
CanvasView.swift
import Cocoa
class CanvasView: NSView {
var Mode: CanvasMode = CanvasMode.Rectangle
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
// super.draw(rect)
// var context: CGContext = NSGraphicsContext.current!.CGContext
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.yellow.cgColor
switch Mode {
case .Line:
print("You got Line.")
default:
print("You got nothing.")
}
}
override func mouseDown(with theEvent: NSEvent) {
print("left mouse")
}
override func rightMouseDown(with theEvent: NSEvent) {
print("right mouse")
}
override func mouseDragged(with theEvent: NSEvent) {
print("mouseDragged")
}
}
CanvasMode.swift
import Cocoa
enum CanvasMode {
case Line
case Rectangle
case Triangle
}
Related
I am trying to make NSViewControler work with NSView. I can't get myView to call any function in myView(NSView).
myView?.myFunctionName does not work and for NSView I get a nil error when I try set backgoundColor (myview?.layer?.backgroundColor = .white)
class ViewController: NSViewController {
#IBOutlet weak public var myview: NSView!
override func viewDidLoad() {
super.viewDidLoad()
myview?.wantsLayer = true
myview?.layer?.backgroundColor = .white
myview?.needsDisplay = true
}
override var representedObject: Any? {
didSet {
//Update the view, if already loaded.
}
}
}
import Cocoa
class myView: NSView {
var backgroundColor: NSColor?
override func draw(_ dirtyRect: NSRect) {
print("draw")
if let bg = backgroundColor {
bg.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
print("error:\(String(describing: backgroundColor))")
self.changeBackgroundColor(color: .blue)
}
}
func changeBackgroundColor(color: NSColor) {
// print("change background\( bounds.size)")
// backgroundColor = color
var path = NSBezierPath()
path = NSBezierPath(rect: NSRect(origin:CGPoint(x: 0, y: 0), size: bounds.size))
// print("\(String(describing: path))")
path.close()
color.setFill()
path.fill()
setNeedsDisplay(self.bounds)
}
}
I want to show NSViewController without using storyboard and Xib. I am already referred lot and can't get work. WindowController is not showing. Please see attached codes & Project.
main.swift
import Cocoa
print("Hello, World!")
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
let ret = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
AppDelegate.Swift
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
WindowController().showWindow(self)
}
}
WindowController.swift
import Cocoa
class WindowController: NSWindowController, NSWindowDelegate {
init() {
let window = Window()
super.init(window: window)
window.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Window.swift
import Cocoa
class Window: NSWindow {
init() {
let screen = NSScreen.main!.frame
let origin = NSPoint(x:screen.width/2, y: screen.height/2)
super.init(contentRect: NSRect(origin: origin, size: NSSize(width: 600, height: 400)), styleMask: [.titled,.resizable,.miniaturizable,.closable], backing: .buffered, defer: false)
self.contentViewController = ViewController()
}
}
ViewController.swift
import Cocoa
class ViewController: NSViewController {
override func loadView() {
view = NSView()
}
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.wantsLayer = true
view.layer!.backgroundColor = NSColor.red.cgColor
}
}
The code is running and viewDidLoad is calling. But window is not visible. Here is attached project. Please help me to find solution. Thanking you in advance!
Update 1.0:
I created WindowController as strong reference. Yes, It's works to keep Window controller instead of deinit. But NSViewController still not showing.
class AppDelegate: NSObject, NSApplicationDelegate {
let controller = WindowController()
func applicationDidFinishLaunching(_ aNotification: Notification) {
controller.showWindow(self)
}
}
The size of the view is (0, 0) and the window is resized to the fit the view. Use a bigger view:
override func loadView() {
view = NSView(frame: NSMakeRect(0, 0, 500, 500))
}
I try to write simple app to draw vertexes and edges between them. I successfully wrote code to add vertexes, delete them and drag around the view. But when I tried to draw a line between them I came across problems. A lot of guide about this are outdated with old versions of Swift or about IOS development so I didn't find how to implement this.
I tried just to draw a line between to points, but nothing worked, also noticed that I want to change coordinates of edge when I drag the vertex.
import Cocoa
var merging = false
var mergingCoords:[CGPoint] = []
class Vertex {
var number:Int
init(number: Int) {
self.number = number
}
}
class Graph {
var size = 0
}
class VertexView: NSBox {
var point: CGPoint = CGPoint(x: 0, y: 0)
required init(coder decoder: NSCoder) {
super.init(coder: decoder)!
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
override func mouseDown(with event: NSEvent) {
if (merging) {
print("Really")
mergingCoords.append(self.point)
if (mergingCoords.count == 2) {
// DRAW AN EDGE
}
} else {
point = event.locationInWindow
}
}
override func mouseUp(with event: NSEvent) {
if (!merging) {
if (event.locationInWindow == point) {
self.removeFromSuperview()
} else {
point = event.locationInWindow
}
}
}
override func mouseDragged(with event: NSEvent) {
if (!merging) {
self.frame = NSRect(x: event.locationInWindow.x - 20, y: event.locationInWindow.y - 20, width: 40, height: 40)
}
}
}
class ViewController: NSViewController {
var graph: Graph = Graph()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear() {
view.window?.makeFirstResponder(self)
}
override func keyDown(with event: NSEvent) {
print(event.characters!)
if (event.characters! == "M") {
merging = !merging;
mergingCoords.removeAll()
}
print(merging)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
override func mouseDown(with event: NSEvent) {
let v = VertexView(frame: NSRect(x: event.locationInWindow.x - 20, y: event.locationInWindow.y - 20, width: 40, height: 40))
v.boxType = NSBox.BoxType.custom
v.point = event.locationInWindow
v.cornerRadius = 20
graph.size = graph.size + 1
self.view.addSubview(v)
}
}
I made a custom UIView() and i'm trying to add a okButton to it.
However. I have added the button but when showing my UIView in one of my ViewControllers, But the button won't work. This is my code:
class Luna: UIView {
let luna = UIView()
let okButton = UIButton()
typealias completionHandler = (_ success:Bool) -> Void
// Not yet implemented
open var touchOutsideToHide: Bool = true;
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
override init(frame: CGRect) {
super.init(frame: frame)
okButton.setTitleColor(.blue, for: .normal)
okButton.setTitle("Okay", for: .normal)
okButton.titleLabel?.font = UIFont(name: "avenirnext-demibold", size: 16)
let gesture:UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.pressButton(_:)))
okButton.addGestureRecognizer(gesture)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
//The target function
#objc func pressButton(_ sender: UIButton){ //<- needs `#objc`
// .. //
print("btn hit")
}
I do not get btn hit in the console and the tap isn't registered
the button is visible, it's added as a subview:
Screenshot
Button have default touch gesture, you don't need to add it. just add target as follow to fix your issue. Create protocol of your customer class and declare delegate for it in same class as follow.
protocol LunaDelegate: class {
func okButtonTapped(sender: UIButton)
}
class Luna: UIView {
let luna = UIView()
let okButton = UIButton()
typealias completionHandler = (_ success:Bool) -> Void
weak var delegate: LunaDelegate?
// Not yet implemented
open var touchOutsideToHide: Bool = true;
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
override init(frame: CGRect) {
super.init(frame: frame)
okButton.setTitleColor(.blue, for: .normal)
okButton.setTitle("Okay", for: .normal)
okButton.titleLabel?.font = UIFont(name: "avenirnext-demibold", size: 16)
okButton.addTarget(self, action: #selector(pressButton(sender:)), for: .touchUpInside)
}
#IBAction func pressButton(sender: UIButton) {
if let selfDelegate = self.delegate {
selfDelegate.okButtonTapped(sender: sender)
}
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Then after implement your code as follow to use your custom view. When your button will pressed then delegate method will be call in your view controller.
Usage:
class LunaViewController: UIViewController, LunaDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let luna = Luna(frame: CGRect.init(x: 100, y: 100, width: 100, height: 40))
luna.delegate = self
self.view.addSubview(luna)
}
// MARK:- Luna Delegate
func okButtonTapped(sender: UIButton) {
print("OK Button Pressed From Luna View")
}
}
This is the proper solution which I have already using in my current project. I hope this will help you.
I've subclassed an NSTextField with the following code:
import Cocoa
class CustomSearchField: NSTextField {
override func draw(_ dirtyRect: NSRect) {
self.wantsLayer = true
let textFieldLayer = CALayer()
self.layer = textFieldLayer
self.backgroundColor = NSColor.white
self.layer?.backgroundColor = CGColor.white
self.layer?.borderColor = CGColor.white
self.layer?.borderWidth = 0
super.cell?.draw(withFrame: dirtyRect, in: self)
}
}
class CustomSearchFieldCell: NSTextFieldCell {
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let minimumHeight = self.cellSize(forBounds: rect).height
let newRect = NSRect(x: rect.origin.x + 25, y: (rect.origin.y + (rect.height - minimumHeight) / 2) - 4, width: rect.size.width - 50, height: minimumHeight)
return super.drawingRect(forBounds: newRect)
}
}
This is all working fine and it draws my NSTextField just as I wanted. The only problem is, as soon as I make some other part of the interface the first responder (clicking outside the NSTextField), the text inside the NSTextField (placeholder or filled in text) is fading out. As soon as I click on it again, it fades back in. I've been searching for quiet a while now, but can't really figure out why this is happening. I just want the text to be visible all the time, instead of fading in and out.
It has to do with the CALayer that I'm adding to accomplish my styling.
Whenever I run the same settings from viewDidLoad on the textfield, it works like a charm. For example:
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSTextField!
override func viewDidLoad() {
initCustomSearchField()
}
private func initCustomSearchField() {
searchField.wantsLayer = true
let textFieldLayer = CALayer()
searchField.layer = textFieldLayer
searchField.backgroundColor = NSColor.white
searchField.layer?.backgroundColor = CGColor.white
searchField.layer?.borderColor = CGColor.white
searchField.layer?.borderWidth = 0
searchField.delegate = self
}
}
draw method should really be used to draw the view not setting the properties.And for your problem don't set to self.layer directly use sublayer instead .
My suggestion for your code:
class CustomTextField :NSTextField {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
textColor = .green
}
}
class CustomTextFieldCell: NSTextFieldCell {
override init(textCell string: String) {
super.init(textCell: string)
setupView()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
func setupView(){
backgroundColor = .red
}
override func drawingRect(forBounds rect: NSRect) -> NSRect {
let newRect = NSRect(x: (rect.width - rect.width/2)/2, y: 0, width: rect.width/2, height: 20)
return super.drawingRect(forBounds:newRect)
}
override func draw(withFrame cellFrame: NSRect, in controlView: NSView) {
super.draw(withFrame: cellFrame, in: controlView)
controlView.layer?.borderColor = NSColor.white.cgColor
controlView.layer?.borderWidth = 2
}
}