I've currently implemented a simple selection box using mouse events and redrawing a rectangle on mouse drag. Here's my code:
-(void)drawRect:(NSRect)dirtyRect
{
if (!NSEqualRects(self.draggingBox, NSZeroRect))
{
[[NSColor grayColor] setStroke];
[[NSBezierPath bezierPathWithRect:self.draggingBox] stroke];
}
}
#pragma mark Mouse Events
- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] fromView:nil];
self.draggingBox = NSMakeRect(pointInView.x, pointInView.y, 0, 0);
[self setNeedsDisplay:YES];
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSPoint pointInView = [self convertPoint:[theEvent locationInWindow] fromView:nil];
_draggingBox.size.width = pointInView.x - (self.draggingBox.origin.x);
_draggingBox.size.height = pointInView.y - (self.draggingBox.origin.y);
[self setNeedsDisplay:YES];
}
- (void)mouseUp:(NSEvent *)theEvent
{
self.draggingBox = NSZeroRect;
[self setNeedsDisplay:YES];
}
Ref: http://cocoadev.com/HowToCreateWalkingAnts
Questions:
Is this the most efficient way to do this? If the view was complex, would it be more efficient to draw a transparent view over the main view instead of continuously redrawing the view for the duration of the mouse drag (http://www.cocoabuilder.com/archive/cocoa/99877-drawing-selection-rectangle.html)? How is this done? I can't seem to find any examples.
swift 3 version:
import Cocoa
import QuartzCore
class myView: NSView {
//MARK:Properties
var startPoint : NSPoint!
var shapeLayer : CAShapeLayer!
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// Drawing code here.
}
override func mouseDown(with event: NSEvent) {
self.startPoint = self.convert(event.locationInWindow, from: nil)
shapeLayer = CAShapeLayer()
shapeLayer.lineWidth = 1.0
shapeLayer.fillColor = NSColor.clear.cgColor
shapeLayer.strokeColor = NSColor.black.cgColor
shapeLayer.lineDashPattern = [10,5]
self.layer?.addSublayer(shapeLayer)
var dashAnimation = CABasicAnimation()
dashAnimation = CABasicAnimation(keyPath: "lineDashPhase")
dashAnimation.duration = 0.75
dashAnimation.fromValue = 0.0
dashAnimation.toValue = 15.0
dashAnimation.repeatCount = .infinity
shapeLayer.add(dashAnimation, forKey: "linePhase")
}
override func mouseDragged(with event: NSEvent) {
let point : NSPoint = self.convert(event.locationInWindow, from: nil)
let path = CGMutablePath()
path.move(to: self.startPoint)
path.addLine(to: NSPoint(x: self.startPoint.x, y: point.y))
path.addLine(to: point)
path.addLine(to: NSPoint(x:point.x,y:self.startPoint.y))
path.closeSubpath()
self.shapeLayer.path = path
}
override func mouseUp(with event: NSEvent) {
self.shapeLayer.removeFromSuperlayer()
self.shapeLayer = nil
}
}
You can use QuartzCore to animate the "marching ants" for you, if you want. This gets you completely out of the world of manually drawing the rubber-banded selection box, too. You just define the path that defines that box, and let Core Animation take care of drawing the box, as well as animating it.
In the XIB's "View Effects" Inspector, turn on "Core Animation", and then you can do something like:
#import <QuartzCore/QuartzCore.h>
#import "CustomView.h"
@interface CustomView ()
@property (nonatomic) NSPoint startPoint;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@end
@implementation CustomView
#pragma mark Mouse Events
- (void)mouseDown:(NSEvent *)theEvent
{
self.startPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
// create and configure shape layer
self.shapeLayer = [CAShapeLayer layer];
self.shapeLayer.lineWidth = 1.0;
self.shapeLayer.strokeColor = [[NSColor blackColor] CGColor];
self.shapeLayer.fillColor = [[NSColor clearColor] CGColor];
self.shapeLayer.lineDashPattern = @[@10, @5];
[self.layer addSublayer:self.shapeLayer];
// create animation for the layer
CABasicAnimation *dashAnimation;
dashAnimation = [CABasicAnimation animationWithKeyPath:@"lineDashPhase"];
[dashAnimation setFromValue:@0.0f];
[dashAnimation setToValue:@15.0f];
[dashAnimation setDuration:0.75f];
[dashAnimation setRepeatCount:HUGE_VALF];
[self.shapeLayer addAnimation:dashAnimation forKey:@"linePhase"];
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
// create path for the shape layer
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, self.startPoint.x, self.startPoint.y);
CGPathAddLineToPoint(path, NULL, self.startPoint.x, point.y);
CGPathAddLineToPoint(path, NULL, point.x, point.y);
CGPathAddLineToPoint(path, NULL, point.x, self.startPoint.y);
CGPathCloseSubpath(path);
// set the shape layer's path
self.shapeLayer.path = path;
CGPathRelease(path);
}
- (void)mouseUp:(NSEvent *)theEvent
{
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
}
@end
This way, Core Animation takes care of the marching ants for you.
This post is a little older but I still found no good solution (Framework) on the web. But I guess this is a task (content selection) everyone have to master someday and there is no API from Apple for it. However I made my own framework it and put on Github: https://github.com/ckteebe/MultiSelectionSuiteFramework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With