macOS 拖拽操作Drag和Drop (二)

英文原文地址

在上一篇macOS 拖拽操作Drag和Drop (一)中主要处理的是接受拖拽的一边,接下来总结拖拽操作发送这边的内容。

所有的拖动source都必须遵循 NSDraggingSource 协议,这里提供两种不同类型的数据:一个标准的 Cocoa 类型(image)和创建的定制类型。

拖拽的source是 ImageSourceView,目的很简单,把这个View上的图拖拽到DestinationView上。

ImageSourceView类需要遵循 NSDraggingSource 和 NSPasteboardItemDataProvider协议 ,因此打开 ImageSourceView.swift 并添加下列的extension:

// MARK: - NSDraggingSource
extension ImageSourceView: NSDraggingSource {
  func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {
    return .generic
  }
  
}
// MARK: - NSPasteboardItemDataProvider
extension ImageSourceView: NSPasteboardItemDataProvider {
  func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) {
    //TODO: Return image data
  }
}
复制代码

开始一个拖拽session

当鼠标在目标图像按下时开始启动,即在override func mouseDown(with event: NSEvent)方法中,所以在 ImageSourceView 类的实现中添加下列方法:

override func mouseDown(with event: NSEvent) {
    let pasteboardItem = NSPasteboardItem()
    pasteboardItem.setDataProvider(self, forTypes: [kUTTypeTIFF])
    
    let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
    draggingItem.setDraggingFrame(self.bounds, contents:snapshot())

    beginDraggingSession(with: [draggingItem], event: event, source: self)
  }
复制代码

1.创建一个 NSPasteboardItem,将这个类作为它的数据提供者。 NSPasteboardItem 是一个“箱子”,可以“运载”被拖拽项目的信息。 NSPasteboardItemDataProvider 根据请求提供数据,提供 TIFF 数据,这是在Cocoa中运载图片的标准形式。

2.创建一个 NSDraggingItem 并将粘贴板的项目赋给它,snapshot() 是NSView的extension,返回一个NSImage。

3.开始拖拽session

拿TIFF数据

为了接受这个数据,需要:

1.在 DestinationView 中更新注册的类型来接受TIFF数据
2.更新 shouldAllowDrag() 来接受TIFF类型
3.更新 performDragOperation(_:) 从粘贴板中拿到图片数据

var nonURLTypes: Set<String>  { return [String(kUTTypeTIFF)] }
var acceptableTypes: Set<String> { return nonURLTypes.union([NSURLPboardType]) }
复制代码

接下来,来到 shouldAllowDrag(:_) 中添加判断,检查 nonURLTypes 集合是否包含了任何从粘贴板接收到的类型,如果有的话,接受拖拽的操作。因为添加了TIFF类型到这个集合,所以这个view就能接受粘贴板的TIFF数据。

else if let types = pasteBoard.types, nonURLTypes.intersection(types).count > 0 {
      //nonURLTypes 集合是否包含了从粘贴板接受到的类型,如果是的话,接受拖拽的操作
      canAccept = true
    }
复制代码

最后,更新 performDragOperation(:) 来解档从粘贴板来的图片数据,Cocoa提供了一个 NSImage 带有 NSPasteboard 参数的构造方法,在performDragOperation(:) 方法中添加

else if let image = NSImage(pasteboard: pasteBoard) {
      delegate?.processImage(image, center: point)
      return true
    }
复制代码

展示图片数据

打开 ImageSourceView.swift,修改func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String)方法

// MARK: - NSPasteboardItemDataProvider
extension ImageSourceView: NSPasteboardItemDataProvider {
  func pasteboard(_ pasteboard: NSPasteboard?, item: NSPasteboardItem, provideDataForType type: String) {
    if let pasteboard = pasteboard, type == String(kUTTypeTIFF), let image = NSImage(named: "unicorn") {
      let finalImage = image.tintedImageWithColor(NSColor.randomColor())
      
      let tiffdata = finalImage.tiffRepresentation
      pasteboard.setData(tiffdata, forType: type) // 图片转为TIFF数据,并放置到粘贴板上
    }
  }
}
复制代码

在这个方法中,做了下面的事情:

1.如果期望的数据类型是 kUTTypeTIFF ,就加载一个名为 unicorn 的图片
2.使用提供的助手方法之一,用任意的颜色给图片染色
3.将图片转为TIFF数据,并将其放置到粘贴板上

拖拽定制类型

注意: 在上一部分,提供了一个标准的数据类型,在接下来这部分中,将定制自己的数据类型。

1.用定制的类型创建一个新的拖拽source
2.更新拖拽destination来识别这个类型
3.更新viewcontroller来响应这个类型

创建拖拽Source

创建 AppActionSourceView.swift,定义定制的拖拽类型和动作的id。

enum SparkleDrag {
  static let type = "com.razeware.StickerDrag.AppAction"
  static let action = "make sparkles"
}
复制代码

为了避免和已存在的类型的冲突,可以定义id像这样: bundle identifier + AppAction,这是一个任意的值,但可以保持它在应用的私有命名空间下,来最小化使用了已存在名称的危险。如果尝试用一个不是 UTI 的类型构建 NSPasteboardItem ,和这个操作将会失败。现在需要让 AppActionSourceView 遵循 NSDraggingSource 协议,在 AppActionSourceView.swift 中添加下列的extension:

// MARK: - NSDraggingSource
extension AppActionSourceView: NSDraggingSource {
  
  func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor
    context: NSDraggingContext) -> NSDragOperation {
    
    switch(context) {
    case .outsideApplication:
      return NSDragOperation()
    case .withinApplication:
      return .generic
    }
  }
}

复制代码

接下来和上面的创建拖拽session的方法一样,在AppActionSourceView中复写mouseDown(with theEvent: NSEvent)方法:

override func mouseDown(with theEvent: NSEvent) {
    
    let pasteboardItem = NSPasteboardItem()
    pasteboardItem.setString(SparkleDrag.action, forType: SparkleDrag.type)
    let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)
    draggingItem.setDraggingFrame(self.bounds, contents:snapshot())
    
    beginDraggingSession(with: [draggingItem], event: theEvent, source: self)
  }
复制代码

这个和 ImageSourceView 在某种程度上的不同之处,投放的数据直接到了粘贴板上,而不是将数据的产生推迟到当view使用 NSPasteboardItemDataProvider 协议接受投放时。

接受新的类型

在DestinationView.swift 添加 SparkleDrag.type 到注册类型中

var nonURLTypes: Set<String> { return [String(kUTTypeTIFF), SparkleDrag.type] } // kUTTypeTIFF类型 + 自定义的SparkleDrag类型
复制代码

现在SparkleDrags就可以被接受了! performDragOperation(:_) 需要一个新的判断

else if let types = pasteBoard.types, types.contains(SparkleDrag.type), let action = pasteBoard.string(forType: SparkleDrag.type) {
      delegate?.processAction(action, center: point)
      return true
    }
复制代码

从粘贴板中抽取了字符串,如果它符合定制的类型,就将动作传回给delegate。

处理动作指令
在 StickerBoardViewController.swift 实现 processAction(_ action: String, center: NSPoint) 方法

func processAction(_ action: String, center: NSPoint) {
    if action == SparkleDrag.action {
      invitationLabel.isHidden = true
      
      if let image = NSImage(named:"star") {
        for _ in 1..<Appearance.numStars {
          
          let maxSize:CGFloat = Appearance.maxStarSize
          let sizeChange = CGFloat(arc4random_uniform(Appearance.randonStarSizeChange))
          let finalSize = maxSize - sizeChange
          let newCenter = center.addRandomNoise(Appearance.randomNoiseStar) //生成一些随机的数字来改变位置
          
          let imageFrame = NSRect(x: newCenter.x, y: newCenter.y, width: finalSize , height: finalSize)
          let imageView = NSImageView(frame:imageFrame)
          
          let newImage = image.tintedImageWithColor(NSColor.randomColor())
          
          imageView.image = newImage
          targetLayer.addSubview(imageView)
        }
      }
    }
  }
复制代码

以上代码做了下列的事:

1.响应已知的动作
2.从bundle中载入一个星星的图片
3.制作一些这个星的图片的拷贝,并...

i.生成一些随机的数字来改变星的位置。
ii.创建一个 NSImageView 并设置它的frame。
iii.这个图片设定一个随机的颜色
iv.放置图片到这个view上。

现在就可以将自定义的类型拖拽到DestinationView上了。

转载于:https://juejin.im/post/5b43108e5188251af6620b76

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值