Draw a line between two objects - swift

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

Related

NSController calling function in NSView

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

NSView draw() being called twice

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
}

uiview not drawing when constrained programmatically

My code is trying to call a class which controlls all the drawing properties and nothing is showing up on the uiview. It's like the class is not being called but I know its being called. I don't know what to do. It is important that canvas covers a specific area and not the entire view thats why I programmed constraint it.
var canvas = Canvas()
override func viewDidLoad() {
super.viewDidLoad()
canvas.backgroundColor = .black
setupLayout()
canvas.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(canvas)
NSLayoutConstraint.activate ([
canvas.trailingAnchor.constraint(equalTo: view.centerXAnchor, constant :150),
canvas.topAnchor.constraint(equalTo: view.centerYAnchor, constant : 0),
canvas.widthAnchor.constraint(equalToConstant: 300),
canvas.heightAnchor.constraint(equalToConstant: 300),
])
}
class Canvas: UIView {
// public function
func undo() {
_ = lines.popLast()
setNeedsDisplay()
}
func clear() {
lines.removeAll()
setNeedsDisplay()
}
fileprivate var lines = [[CGPoint]]()
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(UIColor.red.cgColor)
context.setLineWidth(5)
context.setLineCap(.butt)
lines.forEach { (line) in
for (i, p) in line.enumerated() {
if i == 0 {
context.move(to: p)
} else {
context.addLine(to: p)
}
}
}
context.strokePath()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
lines.append([CGPoint]())
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: nil) else { return }
guard var lastLine = lines.popLast() else { return }
lastLine.append(point)
lines.append(lastLine)
setNeedsDisplay()
}}
Change the below line
guard let point = touches.first?.location(in: nil) else { return }
to this
guard let point = touches.first?.location(in: self) else { return }
The points stored in lines array are corresponding to the superview to which your CanvasView was attached to. You need to store the points corresponding to CanvasView. That's exactly what the self parameter does in place of nil above.

Create a func to undo a UIBezierPath drawing

My code below is in 2 separate classes. From class gooGoo I want to be able to call func change to be used as a undo button to what was drawn on the uiview. Most of the uiview properties are subclassed into classDrawingView.
I have not seen any prior questions on this using a UIBezierPath.
class gooGoo : UIViewController{
var myLayer = CALayer()
var path = UIBezierPath()
var startPoint = CGPoint()
var touchPoint = CGPoint()
var drawPlace = DrawingView()
func Change(){
drawPlace.drawColor = UIColor.black
}
}
class DrawingView: UIView {
var drawColor = UIColor.black
var lineWidth: CGFloat = 5
private var lastPoint: CGPoint!
private var bezierPath: UIBezierPath!
private var pointCounter: Int = 0
private let pointLimit: Int = 128
private var preRenderImage: UIImage!
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
initBezierPath()
}
func takeSnapshotOfView(view:UIView) -> UIImage? {
UIGraphicsBeginImageContext(CGSize(width: view.frame.size.width, height: view.frame.size.height))
view.drawHierarchy(in: CGRect(x: 0, y: 0.0, width: view.frame.size.width, height: view.frame.size.height), afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initBezierPath()
}
func initBezierPath() {
bezierPath = UIBezierPath()
bezierPath.lineCapStyle = CGLineCap.round
bezierPath.lineJoinStyle = CGLineJoin.round
}
// MARK: - Touch handling
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
lastPoint = touch!.location(in: self)
pointCounter = 0
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
let newPoint = touch!.location(in: self)
bezierPath.move(to: lastPoint)
bezierPath.addLine(to: newPoint)
lastPoint = newPoint
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
renderToImage()
setNeedsDisplay()
bezierPath.removeAllPoints()
}
else {
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pointCounter = 0
renderToImage()
setNeedsDisplay()
bezierPath.removeAllPoints()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
touchesEnded(touches!, with: event)
}
// MARK: - Pre render
func renderToImage() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
if preRenderImage != nil {
preRenderImage.draw(in: self.bounds)
}
bezierPath.lineWidth = lineWidth
drawColor.setFill()
drawColor.setStroke()
bezierPath.stroke()
preRenderImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
// MARK: - Render
override func draw(_ rect: CGRect) {
super.draw(rect)
if preRenderImage != nil {
preRenderImage.draw(in: self.bounds)
}
bezierPath.lineWidth = lineWidth
drawColor.setFill()
drawColor.setStroke()
bezierPath.stroke()
}
// MARK: - Clearing
func clear() {
preRenderImage = nil
bezierPath.removeAllPoints()
setNeedsDisplay()
}
// MARK: - Other
func hasLines() -> Bool {
return preRenderImage != nil || !bezierPath.isEmpty
}
}

NSTextField Fades Out After Subclassing

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