原理:在实际操作中以一个canvasView 作为画布,然后定义一个关于图片的数据,画在一个一一对应的NSView中,然后在canvasView最上面加一层拖拽框view 作相关拖拽,旋转操作。例子代码为伪代码,只为说明思路,并未完整实现。读者可以自行根据实际需要自行实现。
1.数据结构
图片数据
@interface ImageData : NSObject {
@property (nonatomic, assign) double rotation;
@property (nonatomic, assign) NSPoint centerPoint; // 在canvasview bounds 上显示 图片的中心点
@property (nonatomic, assign) NSSize mediaSize;a
@property (nonatomic, assign) CGImageRef p_originImageRef_;// 用于画图
@property (nonatomic, assign) CGImageRef p_showImageRef_; // 各种处理之后画图
@property (nonatomic, strong) MediaImageItemView * itemView;// 自定义类用于画图,作为canvasSuperView的subview
}
画布
先定义一个总的背景视图canvasSuperView,其他的view都作为它的子视图
@property (assign) IBOutlet NSView * canvasSuperView;
canvas视图 自定义CanvasView类,实现画背景图方法即可
@property (nonatomic, strong) CanvasView * p_canvasView;
拖拽框视图 自定义CanvasShapeView类,实现画拖拽点方法即可
@property (nonatomic, strong) CanvasShapeView * p_shapeView;
self.p_canvasView = [[CanvasView new] autorelease];
self.p_canvasView.delegate = self;
[self.canvasSuperView addSubview:self.p_canvasView positioned:NSWindowAbove relativeTo:nil];
self.p_canvasView.frame = self.canvasSuperView.bounds;
self.p_shapeView = [[CanvasShapeView new] autorelease];
self.p_shapeView.delegate = self;
[self.canvasSuperView addSubview:self.p_shapeView positioned:NSWindowAbove relativeTo:nil];
self.p_shapeView.frame = self.canvasSuperView.bounds;
下面以加入一张图片到canvasview为例
首先根据图片路径创建一个图片数据对象
ImageData * testData = [ImageData createImageDataWithPath:path];
+ (ImageData *)createImageDataWithPath:(NSString *)path {
ImageData * item = [[ImageData new] autorelease];
if ([[NSFileManager defaultManager] fileExistsAtPath:path] == NO) {
return nil;
}
NSImage * image = [[[NSImage alloc] initWithContentsOfFile:self.path] autorelease];
self.p_originImageRef_ = createCGImageRefFromNSImage(image);
if (item.originImageRef == NULL) {
return nil;
}
item.centerPoint = NSMakePoint(50, 50);
NSSize size = NSMakeSize(CGImageGetWidth(item.originImageRef), CGImageGetHeight(item.originImageRef));
item.originImageSize = size;
item.mediaSize = size;
item.rotation = 0;
return item;
}
- (void)reFlushImageDataItemViewInSuperview:(ImageData *)imageItem {
if (testData.itemView == nil) {
MediaImageItemView * view = [[MediaImageItemView new] autorelease];
view.imageItem = imageItem;
testData.itemView = view;
}
// 先rotation置0 ,设置好frame 在调整回原来的旋转度数
[testData.itemView setFrameCenterRotation:0];
NSRect frame = [CommonFunction rectForCenter:testData.centerPoint size:testData.mediaSize];
[testData.itemView setFrame:frame];
[testData.itemView setFrameCenterRotation:testData.rotation];
if ([testData.itemView superview] != self.canvasSuperView) {
[self.canvasSuperView addSubview:testData.itemView positioned:NSWindowAbove relativeTo:nil];
}
// 保持拖拽框的view在最上面,好相应鼠标事件
[self.canvasSuperView addSubview:self.shapeView positioned:NSWindowAbove relativeTo:nil];
[self.canvasSuperView setNeedsDisplay:YES];
}
根据canvassuperview的bounds ,调整第一次导入到canvas,imagedata 的 中心位置和显示大小
testData.originImageSize = NSMakePoint(..)
testData.mediaSize = size * scale; // 可自定义缩放
2.三个view的各自主要实现部分
2.1 MediaImageItemView.m 中只用画图即可
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
@autoreleasepool {
CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
NSRect rect = self.bounds;
CGContextDrawImage(imageContext, rect, self.imageItem.showImageRef);
}
}
2.2 canvasView 的实现,也是作为画背景图,这里无关痛痒,不画亦可。
3.3 shapeview 的实现,这里就是主要的拖拽实现
定义一个拖拽的枚举类型,通过鼠标点击point判断选择的是那个点z作为拖拽点
typedef NS_ENUM(NSInteger, CanvasShapeViewDragType) {
CanvasShapeViewDrag_None,
CanvasShapeViewDrag_TopLeft,
CanvasShapeViewDrag_TopRight,
CanvasShapeViewDrag_BottomRigth,
CanvasShapeViewDrag_BottomLeft,
CanvasShapeViewDrag_MiddleLeft,
CanvasShapeViewDrag_MiddleRight,
CanvasShapeViewDrag_MiddleTop,
CanvasShapeViewDrag_MiddleBottom,
CanvasShapeViewDrag_MiddleCenter,
};
通过testdata的itemview
// 框画的时候用,这里可以选择画在itemview上 ,也可以选择画在shapeview上,这里为了实际好操作,把9个拖拽点画在了itemview上,然后通过origin = [self convertPoint:origin fromView:self.p_mediaItemView];转换到shapeview上,在画。
- (NSRect)contentRect {
NSRect rect = self.p_mediaItemView.bounds;
return rect;
}
// 移动的时候用 比如拖的是中间部分,那个就要改变itemview的frame
- (NSRect)contentFrame {
NSRect rect = self.p_mediaItemView.frame;
return rect;
}
拖拽核心部分在于判断mousedown点到哪个点以及之后的处理,各个function未实现的如字面意思
- (void)mouseDown:(NSEvent *)theEvent {
NSPoint mouse = [self convertPoint:theEvent.locationInWindow fromView:nil];
[self checkIfMouseDownOnMedia:mouse];
if (self.currentMedia == nil) {
return;
}
CanvasShapeViewDragType dragType = [self checkDragTypeWithPointOnShape:mouse];
[self setNeedsDisplay:YES];
NSRect oldFrame = self.contentFrame;
if (dragType != CanvasShapeViewDrag_None) {
while ( (theEvent = [self.window nextEventMatchingMask:NSLeftMouseDraggedMask|NSLeftMouseUpMask]) && ([theEvent type] == NSLeftMouseDragged)) {
@autoreleasepool {
NSPoint currentPoint, lastPoint = NSZeroPoint;
if (dragType == CanvasShapeViewDrag_MiddleCenter) {
currentPoint = [self.superview convertPoint:theEvent.locationInWindow fromView:nil];
lastPoint = [self.superview convertPoint:mouse fromView:self];
} else {
currentPoint = [self.p_mediaItemView convertPoint:theEvent.locationInWindow fromView:nil];
lastPoint = [self.p_mediaItemView convertPoint:mouse fromView:self];
}
[self startDragAction:dragType from:lastPoint to:currentPoint oldFrame:oldFrame];
}
}
}
if ([theEvent type] == NSLeftMouseUp) {
if ([theEvent clickCount]==1) {
[self.nextResponder mouseUp:theEvent];
}
[self setNeedsDisplay:YES];
}
}
- (void)startDragAction:(CanvasShapeViewDragType)dragType from:(NSPoint)last to:(NSPoint)mouse oldFrame:(NSRect)oldCropRect {
NSSize offset = NSMakeSize(mouse.x - last.x, mouse.y - last.y);
NSRect rect = NSZeroRect;
if (dragType==CanvasShapeViewDrag_MiddleCenter){
rect = oldCropRect;
rect.origin.x += offset.width;
rect.origin.y += offset.height;
} else {
// 实现其他点时,怎么改变当前itemview的frame
rect = …;
}
[self updateCurrentMediaViewFrameWhenShapeDraged:rect];
[self setNeedsDisplay:YES];
}
要特别注意的就是这里得到rect之后在有旋转的情况下,怎么把得到的rect改动到itemview上去
self.selectedMediaItemView 即为testdata的itemview
- (void)updateCurrentMediaViewFrameWhenShapeDraged:(NSRect)rect {
self.selectedMediaItemView.frame = rect;
[self.selectedMediaItemView setFrameCenterRotation:0];
rect = self.selectedMediaItemView.frame;
NSPoint centerOnRect = NSMakePoint(NSMidX(rect), NSMidY(rect));
[self mediaItem:self.selectedMedia centerPoint:centerOnRect]; // self.selectedMedia.centerPoint = centerOnRect;
[self mediaItem:self.selectedMedia mediaSize:rect.size]; // self.selectedMedia.mediaSize = rect.size;
[self.selectedMediaItemView setFrameCenterRotation:self.selectedMedia.rotation];
}
旋转的实现 改变rotate值即可。关键在于之后刷新图的位置调用
reFlushImageDataItemViewInSuperview: (上面有)注意先把view的frameCenterRotation值0,设置好之后还原。