OSX Swift , Draw a simple line with a push of a button - macos

Have looked a lot for this but nothing found to match.
Real simple I want to draw a line buy a push of a button.
I have a class which is from NSView to do the drawing, this monitors a var and if it changes to redraw the view.
I can get the mouse click to call the view by changing the value and it draws just fine. But I cant get the button to work. It calls the drawing method but the view is not updated ???
I know its got to be something simple, but I just cannot find it.
Thanks in advance.
Below is my View class and View Controller.
//
// LineDrawer.swift
// TestDraw3
//
// Created by Colin Wood on 21/04/17.
// Copyright © 2017 Colin Wood. All rights reserved.
//
import Cocoa
class LineDrawer : NSView {
// Vars
var newLinear = NSBezierPath()
public var QQ: Int = 0 { // every time QQ changes redraw information in QQ
didSet{
boom() // drawing method
}
}
// Methods
override func draw(_ dirtyRect: NSRect) {
NSColor.red.set()
newLinear.lineWidth = 5
newLinear.lineCapStyle = NSLineCapStyle.roundLineCapStyle
newLinear.stroke()
}
func boom(){ // draw a simple line
Swift.print("In BooM")
Swift.print(QQ)
let lastPt = NSPoint(x: QQ, y: 1)
newLinear.move(to: lastPt)
let newPt = NSPoint(x: QQ, y: 50)
newLinear.line(to: newPt)
needsDisplay = true
}
override func mouseDown(with theEvent: NSEvent) {
QQ = QQ + 7 // just add 7 so you can see it being drawn after didSet
}
}
//
// ViewController.swift
// TestDraw3
//
// Created by Colin Wood on 15/04/17.
// Copyright © 2017 Colin Wood. All rights reserved.
//
import Cocoa
class ViewController: NSViewController {
// Vars
#IBOutlet weak var textField: NSTextField!
// Methods
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func button(_ sender: Any) {
let v = LineDrawer()
v.QQ = 300 // change QQ to call didSet
}
}

You need to add your LineDrawer as a subview. A good place, to add it, is in the viewDidLoad method:
private var line: LinerDrawer!
override func viewDidLoad() {
super.viewDidLoad()
line = LinerDrawer()
line.frame = NSMakeRect(0, 0, 200, 200)
line.QQ = 300
line.isHidden = true
view.addSubview(line)
}
And then finally show it when you hit the button:
#IBAction func button(_ sender: Any) {
line.isHidden = false
}
You still need to work out the correct frame for your LineDrawer view.

Related

Additional view in NSCollectionViewItem pauses dragEvent in CollectionViewController

I am trying to implement drop delegates on a NSCollectionViewController and having issues using a custom NSCollectionViewItem with an additional View Layer I've added onto the CollectionView Item. FWIW, The additional view is used draw a dashed border to indicate a drop area.
The drag event works fine on this collectionItem, and all other collectionItems without this view when it is hidden, but as soon as the drag event occurs on top of this view, the drag event pauses.
The drag event resumes as soon as the mouse is dragged outside of the view, but nothing happens if I release the drag while the mouse is over the view.
I would love to know what is happening here and how to prevent the custom view from "stealing" the mouse event from the CollectionViewContoller.
Delegate Method on DropViewController
func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
print("1")
if proposedDropIndexPath.pointee.item <= self.destinationDirectoryArray.count {
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
} else if proposedDropIndexPath.pointee.item == self.destinationDirectoryArray.count {
//There's some stuff here validating the URL removed for brevity. It works okay when the focus is outside the view, but happy to add back in if helpful
if proposedDropOperation.pointee == NSCollectionView.DropOperation.on {
return .move
}
}
return[]
}
Configuring Collection View
func configureCollectionView() {
let flowLayout = NSCollectionViewFlowLayout()
flowLayout.minimumInteritemSpacing = 8.0
flowLayout.minimumLineSpacing = 8.0
destinationCollectionView.delegate = self
destinationCollectionView.dataSource = self
destinationCollectionView.register(NSNib(nibNamed: "DestinationCollectionItem", bundle: nil), forItemWithIdentifier: directoryItemIdentifier)
destinationCollectionView.collectionViewLayout = flowLayout
destinationCollectionView.registerForDraggedTypes([.fileURL])
destinationCollectionView.setDraggingSourceOperationMask(NSDragOperation.move, forLocal: true)
}
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
}
}
Custom Border View - Applied custom class in Xib and linked to File's Owner
class BorderedView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path : NSBezierPath = NSBezierPath(roundedRect: self.bounds, xRadius: 10.0, yRadius: 10.0)
path.addClip()
let dashHeight: CGFloat = 2
let dashLength: CGFloat = 7
let dashColor: NSColor = .lightGray
// setup the context
let currentContext = NSGraphicsContext.current!.cgContext
currentContext.setLineWidth(dashHeight)
currentContext.setLineDash(phase: 0, lengths: [dashLength])
currentContext.setStrokeColor(dashColor.cgColor)
// draw the dashed path
let cgPath : CGPath = CGPath(roundedRect: NSRectToCGRect(self.bounds), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil)
currentContext.addPath(cgPath)
currentContext.strokePath()
}
}
Well - I solved this one pretty quick.
While I previously tried adding unregisterDraggedTypes() to the backgroundLayer, the issue turned out to also be occurring on the image layer. I applied it to both the Image and backgroundLayer and it works now.
Collection View Item Setup
class DestinationCollectionItem: NSCollectionViewItem {
#IBOutlet weak var backgroundLayer: NSView!
override func viewDidLoad() {
super.viewDidLoad()
self.highlightState = .none
view.wantsLayer = true
view.layer?.cornerRadius = 8.0
backgroundLayer.isHidden = true
backgroundLayer.unregisterDraggedTypes()
self.imageView?.unregisterDraggedTypes()
self.textField?.unregisterDraggedTypes()
}
}

Reading right mouse clicks number of clicks

I have been experimenting with mouse clicks. I am ok with left mouse clicks getting raw values etc etc. I now want to add right mouse clicks. I have setup a basic example. What i would like to achieve if there is one mouse right click it performs one function, and if there is two mouse right clicks it performs a different function. The problem is if you do two mouse clicks it obviously cannot differentiate between the two and so fire the one mouse click function before performing the second mouse function.
I was thinking of maybe using a timer of some sort to record the number of click. But i end up going round in circles as i just seem to start the timers over and over. I'm hoping some one might help. thanks for reading here is the code.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var MyView: NSView!
override func windowDidLoad() {
super.windowDidLoad()
//Initialize mouse for Right Click numberOfClicksRequired = 1
let recogRightClick1 = NSClickGestureRecognizer(target: self, action: #selector(oneMouseClick))
recogRightClick1.buttonMask = 0x2
recogRightClick1.numberOfClicksRequired = 1
MyView.addGestureRecognizer(recogRightClick1)
//Initialize mouse for Right ClicknumberOfClicksRequired = 2
let recogRightClick2 = NSClickGestureRecognizer(target: self, action: #selector(twoMouseClick(myrec:myRightClick:)))
recogRightClick2.buttonMask = 0x2
recogRightClick2.numberOfClicksRequired = 2
MyView.addGestureRecognizer(recogRightClick2)
}//EO Overide
func oneMouseClick(myrec: NSPanGestureRecognizer,myRightClick:NSClickGestureRecognizer){
let rightClick = myRightClick.state.rawValue
print("oneMouseClick",rightClick)
}
func twoMouseClick(myrec: NSPanGestureRecognizer,myRightClick:NSClickGestureRecognizer){
let rightClick = myRightClick.state.rawValue
print("twoMouseClick",rightClick)
}
}//EnD oF thE wORld
UPDATE
I have re generated the code following the advice given. Now the code reflects more of what i wanted to do. My only problem is that I would like all the mouse operations to be triggered only inside 'myView' rather than within the main window. I thought it might have something to do with first responder but that doesn't seem to work. Again any thought would be appreciated. Please excuse any bad code i'm self taught.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
var mouseX:CGFloat = 0
var mouseY:CGFloat = 0
override func windowDidLoad() {
myView.window?.becomeFirstResponder()
myView.window?.acceptsMouseMovedEvents = true
super.windowDidLoad()
myView.wantsLayer = true
myView.layer?.backgroundColor = CGColor(red: 0.05, green: 0.57, blue: 0.80, alpha: 0.6)
NSEvent.addLocalMonitorForEvents(matching:.leftMouseDown){
self.mouseEventFunction(data: 1)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.leftMouseUp){
self.mouseEventFunction(data:2)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.rightMouseDown){
self.mouseEventFunction(data: 3)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.rightMouseUp){
self.mouseEventFunction(data: 4)
return $0
}
NSEvent.addLocalMonitorForEvents(matching:.mouseMoved) {
self.mouseX = NSEvent.mouseLocation().x
self.mouseY = NSEvent.mouseLocation().y
return $0 }
}//EO Overide
func mouseEventFunction (data: Int){
switch data{
case 1 :
print("LeftMouseDown")
case 2 :
print("LeftMouseUp")
case 3 :
print("RightMouseDown")
case 3 :
print("RightMouseUP")
default: break
}
if data == 1 {print("mouseX",mouseX,"mouseY",mouseY)}
}//eo mouseEvent
}//EnD oF thE wORld
UPDATE 2
I have now updated subClassing the view controller, so the mouse clicks are now only working in myView. I'm still having problems with 'func mouseDragged' What i need to achieve is the bottom left of my view is x = 0 and Y = 0. I had a try with converting but thats not working. hoping someone might guide me. thanks for reading here is the updated code.
Xcode 8 Swift 3 Mac OSX Sierra NOT IOS.. NOT IOS
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
override func windowDidLoad() {
myView.window?.becomeFirstResponder()
myView.window?.acceptsMouseMovedEvents = true
window?.contentView?.addSubview(myView)
super.windowDidLoad()
}//EO Overide
}//EnD oF thE wORld
class testView: NSView {
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGray
backgroundColor.set()
NSBezierPath.fill(bounds)
}
override func mouseDragged(with theEvent: NSEvent) {
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, to: self)
Swift.print("myLocationInWindow",myLocationInWindow,"location",location)
}
override func mouseDown(with theEvent: NSEvent) {
Swift.print("mouseDown")
}
override func mouseUp(with theEvent: NSEvent) {
Swift.print("mouseUp clickCount: \(theEvent.clickCount)")
}
}//eo testView
To define mouse inside view you use
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, from: nil)
where nil is the window
here is the final code
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
override func windowDidLoad() {
super.windowDidLoad()
}//EO Overide
}//EnD oF thE wORld
class testView: NSView {
override func draw(_ dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGray
backgroundColor.set()
NSBezierPath.fill(bounds)
}
override func mouseDragged(with theEvent: NSEvent) {
let myLocationInWindow = theEvent.locationInWindow
let location = convert(myLocationInWindow, from: nil)
Swift.print("location",location)
}
override func mouseDown(with theEvent: NSEvent) {
Swift.print("mouseDown")
}
override func mouseUp(with theEvent: NSEvent) {
Swift.print("mouseUp clickCount: \(theEvent.clickCount)")
}
}//eo testView

passing variables to NSView

I'm trying to pass the contents of an array to an NSView to display rectangles x positions.
I have set up a version using random numbers and a button and reduced the code to a minimum so hopefully it's more readable . I have tried creating the array as a Global Variable. I have also tried reversing the code from this answer.Pass data between view controllers.I have inserted a comment: "This is what I would like to achieve" which explain what i'm trying to achieve.
I'm hoping someone might help ? thanks
Swift 3 Xcode 8.1 OSX Mac OS not iOS
//
// MainWindowController.swift
import Cocoa
class MainWindowController: NSWindowController {
#IBOutlet weak var myView: NSView!
var myArray = [Int]()
override func windowDidLoad() {
super.windowDidLoad()
//just for testing
for i in 0..<6{
myArray.append(i*10)
}
}//EO Overide
//just for testing
#IBAction func myButton(_ sender: AnyObject) {
myFunc()
}
func myFunc(){
for i in 0..<6{
let diceRoll = Int(arc4random_uniform(6) + 1)
myArray[i]=diceRoll * 10
}
myView.needsDisplay = true
}//EO myFunc
}//EnD oF thE wORld
class myGUI: NSView {
override func draw(_ dirtyRect: NSRect) {
for i in..<6{
let fromArray = myArray[i]
let box1 = NSMakeRect(CGFloat(fromArray),0,4,60)//This is what i want to do
let box1Color = NSColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1.0)
let box1Path: NSBezierPath = NSBezierPath(rect: box1)
box1Color.set()
box1Path.fill()
}//EO For
}}
Since you have a strong reference to the subclass of NSView just add a property:
class myGUI: NSView {
var xPosArray = [Int]()
override func draw(_ dirtyRect: NSRect) {
for xPos in xPosArray { // please avoid ugly C-style index based loops
let box1 = NSMakeRect(CGFloat(xPos), 0, 4, 60)
...
and simply set it in the view controller
func myFunc(){
for i in 0..<6 {
let diceRoll = Int(arc4random_uniform(6) + 1)
myArray[i]=diceRoll * 10
}
myView.xPosArray = myArray
myView.needsDisplay = true
}
Consider that class names are supposed to start with an uppercase letter.

Spin button infinitely: Swift

I found this code that allows you to rotate/spin 90 degrees when button is pushed in Swift. But what I would like to do is rotate/spin the button infinitely when it is pushed, and stop spinning when the button is pushed again. Here is the code I have:
import UIKit
class ViewController: UIViewController {
#IBOutlet var otherbutton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Rotate(sender: AnyObject) {
UIView.animateWithDuration(1.0,
animations: ({
self.otherbutton.transform = CGAffineTransformMakeRotation(90)
}))
}
}
You can use a CABasicAnimation to animate it infinitely as follow:
class ViewController: UIViewController {
#IBOutlet weak var spinButton: UIButton!
// create a bool var to know if it is rotating or not
var isRotating = false
#IBAction func spinAction(sender: AnyObject) {
// check if it is not rotating
if !isRotating {
// create a spin animation
let spinAnimation = CABasicAnimation()
// starts from 0
spinAnimation.fromValue = 0
// goes to 360 ( 2 * π )
spinAnimation.toValue = M_PI*2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make it spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// and the animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
spinButton.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
spinButton.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}
It should be the same as yours.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var spinButtton: UIButton!
var isRotating = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func spinAction(sender: AnyObject) {
if !isRotating {
let spinAnimation = CABasicAnimation()
// start from 0
spinAnimation.fromValue = 0
// goes to 360
spinAnimation.toValue = M_1_PI * 2
// define how long it will take to complete a 360
spinAnimation.duration = 1
// make spin infinitely
spinAnimation.repeatCount = Float.infinity
// do not remove when completed
spinAnimation.removedOnCompletion = false
// specify the fill mode
spinAnimation.fillMode = kCAFillModeForwards
// animation acceleration
spinAnimation.timingFunction = CAMediaTimingFunction (name: kCAMediaTimingFunctionLinear)
// add the animation to the button layer
spinAnimation.layer.addAnimation(spinAnimation, forKey: "transform.rotation.z")
} else {
// remove the animation
spinButtton.layer.removeAllAnimations()
}
// toggle its state
isRotating = !isRotating
}
}

iAd in xcode 6 with Swift

I'm working to implement a banner ad in the scene, but it always reports "Thread 1: EXC_BREAKPOINT(code=EXC_ARM_BREAKPOINT, subcode=Oxdefe) and the program stops running.
I referenced Mr. T's answer in another question about iAd("Swift - ADBannerView") but still couldn't make it.
The code looks like this:
import UIKit
import SpriteKit
import iAd
class GameViewController: UIViewController, ADBannerViewDelegate {
#IBOutlet var adBannerView: ADBannerView
override func viewDidLoad() {
super.viewDidLoad()
println("view loaded")
//iAd
self.canDisplayBannerAds = true
self.adBannerView.delegate = self
self.adBannerView.alpha = 0.0
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
skView.presentScene(scene)
}
}
//iAd
func bannerViewWillLoadAd(banner: ADBannerView!) {
println("sort of working1")
}
func bannerViewDidLoadAd(banner: ADBannerView!) {
self.adBannerView.alpha = 1.0
println("sort of working2")
}
func bannerViewActionDidFinish(banner: ADBannerView!) {
println("sort of working3")
}
func bannerViewActionShouldBegin(banner: ADBannerView!, willLeaveApplication willLeave: Bool) -> Bool {
println("sort of working4")
return true
}
func bannerView(banner: ADBannerView!, didFailToReceiveAdWithError error: NSError!) {
}
}
And I created an ADBannerView in the Main.storyboard and linked it with the #IBOutlet adBannerView.
Anyone helps me figure out?
This is how I did it, possibly not all of it is necessary.
I did not use the banner in the Storyboard, so the IBOutlet is not necessary.
Also, if you manually create a banner, you do not need to set self.canDisplayBannerAds
This function (ported from ObjC) is how I display the ads.
func loadAds(){
adBannerView = ADBannerView(frame: CGRect.zeroRect)
adBannerView.center = CGPoint(x: adBannerView.center.x, y: view.bounds.size.height - adBannerView.frame.size.height / 2)
adBannerView.delegate = self
adBannerView.hidden = true
view.addSubview(adBannerView)
}
This is called in viewDidLoad. Then, in the didLoadAd delegate method, I set adBannerView.hidden = false and in didFailToReceiveAdWithError, adBannerView.hidden = true
I think hidden is better than alpha in this situation, as it feels more natural. I believe (but am not sure) that when hidden, the view is not drawn at all by the GPU, while with an alpha of 0, it is still drawn, but made invisible (correct me if I am wrong).
This is my setup, and it worked for me, so hopefully it will work in your case too!

Resources