Objective-C/AlignmentGuides/AAPLAlignmentGuidesView.m
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
AlignmentGuidesView shows how to use NSAlignmentFeedbackFilter to align a box to the edges of its container and the container's center. |
The sample shows how to feed the filter using both a tracking loop (see mouseDown) and a gesture recognizer (see panGestureUpdated). These two methods feed the filter the latest event information, and then call into the appropriate handler for mouse down/dragged/up. |
*/ |
#import "AAPLAlignmentGuidesView.h" |
@implementation AAPLAlignmentGuidesView |
- (id)initWithCoder:(nonnull NSCoder *)coder { |
self = [super initWithCoder:coder]; |
if (self) { |
self.feedbackFilter = [NSAlignmentFeedbackFilter new]; |
self.boxFrame = NSMakeRect(180.0, 210.0, 200.0, 200.0); |
self.dragOriginOffset = NSZeroPoint; |
self.drawCenterXAsHoldingItem = NO; |
self.drawCenterYAsHoldingItem = NO; |
} |
return self; |
} |
// *** Updating the filter using a tracking loop. mouseDown tracks the entire mouse drag, updating the alignment filter with each event. |
- (void)mouseDown:(nonnull NSEvent *)theEvent { |
if (self.eventHandlingButton.indexOfSelectedItem != 0) { |
return; |
} |
if (![self handleMouseDown:[self convertPoint:theEvent.locationInWindow fromView:nil]]) { |
return; |
} |
[self.feedbackFilter updateWithEvent:theEvent]; |
const NSEventMask mask = [NSAlignmentFeedbackFilter inputEventMask]|NSEventMaskLeftMouseUp; |
[self.window trackEventsMatchingMask:mask timeout:NSEventDurationForever mode:NSEventTrackingRunLoopMode handler:^(NSEvent *event, BOOL *stop) { |
[self.feedbackFilter updateWithEvent:event]; |
NSRect fromFrame = self.boxFrame; |
switch (event.type) { |
case NSEventTypeLeftMouseUp: |
[self handleMouseUp]; |
(*stop) = YES; |
break; |
case NSEventTypeLeftMouseDragged: |
[self handleMouseDragged:[self convertPoint:event.locationInWindow fromView:nil]]; |
break; |
default: |
break; |
} |
self.boxFrame = [self performAlignmentFrom:fromFrame draggedTo:self.boxFrame]; |
[self setNeedsDisplay:YES]; |
}]; |
} |
// *** Updating the filter using a pan gesture recognizer. panGestureUpdated updates the alignment filter for each movement. |
- (BOOL)gestureRecognizerShouldBegin:(NSGestureRecognizer *)recognizer { |
// If we aren't using the gesture recognizer, bail here since mouseDown will handle it. |
if ([self.eventHandlingButton indexOfSelectedItem] != 1) { |
return NO; |
} |
// Return whether or not a draggable object was clicked |
return [self handleMouseDown:[recognizer locationInView:self]]; |
} |
- (IBAction)magnificationChanged:(NSSlider *)slider { |
self.enclosingScrollView.magnification = slider.doubleValue; |
} |
- (IBAction)panGestureUpdated:(NSPanGestureRecognizer *)recognizer { |
// Store the last frame shown to the user |
NSRect fromFrame = self.boxFrame; |
switch (recognizer.state) { |
case NSGestureRecognizerStateBegan: |
[self.feedbackFilter updateWithPanRecognizer:recognizer]; |
break; |
case NSGestureRecognizerStateChanged: |
[self.feedbackFilter updateWithPanRecognizer:recognizer]; |
[self handleMouseDragged:[recognizer locationInView:self]]; |
break; |
case NSGestureRecognizerStateEnded: |
case NSGestureRecognizerStateCancelled: |
[self handleMouseUp]; |
break; |
default: |
break; |
} |
// Now perform alignment |
// It's important to do this for every action so that periodic events can cause alignment to happen (consider case of fast-moving cursor stopping over a guide) |
self.boxFrame = [self performAlignmentFrom:fromFrame draggedTo:self.boxFrame]; |
[self setNeedsDisplay:YES]; |
} |
// *** Mouse down/dragged/up handling. On drag update where the item would be without any alignment (its offset), and then call into performAlignment. |
// Generic handler for starting drag. Returns whether or not a draggable object was clicked |
- (BOOL)handleMouseDown:(NSPoint)location { |
self.dragOriginOffset = NSMakePoint(location.x-self.boxFrame.origin.x, location.y-self.boxFrame.origin.y); |
if (!NSPointInRect(location, self.boxFrame)) { |
return NO; |
} |
// Start a drag |
return YES; |
} |
// Generic handler for moving a drag |
- (void)handleMouseDragged:(NSPoint)location { |
self.boxFrame = NSMakeRect(location.x-self.dragOriginOffset.x, location.y-self.dragOriginOffset.y, self.boxFrame.size.width, self.boxFrame.size.height); |
self.boxFrame = [self constrainRectToBounds:self.boxFrame]; |
if (self.useGridButton.state == NSOnState) { |
NSPoint origin; |
origin.x = round(self.boxFrame.origin.x/10.0) * 10.0; |
origin.y = round(self.boxFrame.origin.y/10.0) * 10.0; |
self.boxFrame = NSMakeRect(origin.x, origin.y, self.boxFrame.size.width, self.boxFrame.size.height); |
} |
[self scrollRectToVisible:NSMakeRect(location.x, location.y, 1.0, 1.0)]; |
} |
// Generic handler for stopping a drag |
- (void)handleMouseUp { } |
- (NSRect)performAlignmentFrom:(NSRect)movedFrom draggedTo:(NSRect)draggedTo { |
NSRect newRect = draggedTo; |
// *** Align the edges of the box to the edges of the container |
NSMutableArray<id<NSAlignmentFeedbackToken>> *preparedAlignments = [NSMutableArray array]; |
id<NSAlignmentFeedbackToken> token; |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForHorizontalMovementInView:self previousX:NSMinX(movedFrom) alignedX:0.0 defaultX:NSMinX(draggedTo)])) { |
newRect.origin.x = 0.0; |
[preparedAlignments addObject:token]; |
} |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForHorizontalMovementInView:self previousX:NSMaxX(movedFrom) alignedX:self.bounds.size.width defaultX:NSMaxX(draggedTo)])) { |
newRect.origin.x = self.bounds.size.width-newRect.size.width; |
[preparedAlignments addObject:token]; |
} |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForVerticalMovementInView:self previousY:NSMinY(movedFrom) alignedY:0.0 defaultY:NSMinY(draggedTo)])) { |
newRect.origin.y = 0.0; |
[preparedAlignments addObject:token]; |
} |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForVerticalMovementInView:self previousY:NSMaxY(movedFrom) alignedY:self.bounds.size.height defaultY:NSMaxY(draggedTo)])) { |
newRect.origin.y = self.bounds.size.height-newRect.size.height; |
[preparedAlignments addObject:token]; |
} |
// *** Align the centers of the box to the centers of the container |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForHorizontalMovementInView:self previousX:NSMidX(movedFrom) alignedX:NSMidX(self.bounds) defaultX:NSMidX(draggedTo)])) { |
newRect.origin.x = NSMidX(self.bounds)-newRect.size.width/2.0; |
[preparedAlignments addObject:token]; |
self.drawCenterXAsHoldingItem = YES; |
} |
else { |
self.drawCenterXAsHoldingItem = NO; |
} |
if ((token = [self.feedbackFilter alignmentFeedbackTokenForVerticalMovementInView:self previousY:NSMidY(movedFrom) alignedY:NSMidY(self.bounds) defaultY:NSMidY(draggedTo)])) { |
newRect.origin.y = NSMidY(self.bounds)-newRect.size.height/2.0; |
[preparedAlignments addObject:token]; |
self.drawCenterYAsHoldingItem = YES; |
} |
else { |
self.drawCenterYAsHoldingItem = NO; |
} |
[self.feedbackFilter performFeedback:preparedAlignments performanceTime: NSHapticFeedbackPerformanceTimeDrawCompleted]; |
return newRect; |
} |
// *** Your app can draw however it wants. The box could be a subview, or anything really. Here we're drawing using bezier paths. |
- (void)drawRect:(NSRect)dirtyRect { |
[super drawRect:dirtyRect]; |
[self drawHorizontalGuide:NSMidY(self.bounds) holdingItem:self.drawCenterYAsHoldingItem]; |
[self drawVerticalGuide:NSMidX(self.bounds) holdingItem:self.drawCenterXAsHoldingItem]; |
[self drawBox:self.boxFrame drawCenterAlignmentGuides:true]; |
} |
@end |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-09-19