手把手教你实现macOS应用文件拖拽进窗口功能-批量生成APP的多尺寸icon实战

本文始发于我的博文手把手教你实现macOS应用文件拖拽进窗口功能-批量生成APP的多尺寸icon实战,现转发至此。

目录

  • 前言
  • 拖拽功能
    • Source 、 Destination 和 Dragging Session
    • 需要处理的事情
  • Demo 功能实现
    • 功能拆解
    • 实现
    • 文件的读写权限
    • NSView 设置问题
    • 关于 Retina 设备生成图片大小问题
  • 总结

前言

最近公司项目的 APP 更换 icon,UI 设计师提供的素材缺少几个尺寸,因为不清楚具体需要哪些尺寸,且经过交流发现,UI 设计师生成不同尺寸时,是一个一个生成的,这样效率比较低,于是想做个小工具提高下这种工作的效率,当作玩玩 macOS 开发咯(之前开发过对接 jira 的批量新建分配任务的小工具且一直在使用)。

关于批量生成 icon 的,朋友推荐了一个在线工具,这个功能已经比较完善,可以了解下。当然,自己开发的话可以定制。

拖拽功能

讲讲拖拽那点事,更多具体信息可以看👇的官方文档。

苹果官方文档 Drag and Drop

Source 、 Destination 和 Dragging Session

操作拖拽,那么就涉及到三个元素,拖拽的起点,称为源 Source;拖拽的终点,称为目的地 Destination;拖拽的对象,称为 Item 吧。源对应协议 NSDraggingSource,目的地对应协议 NSDraggingDestination;拖拽过程就是一个 Dragging Session;拖拽的对象是 NSDraggingItem,放在 NSPasteboard 中。

大概过程:

  1. 从 Source 开始拖拽,Dragging Session 生成并进行
  2. 选择拖拽对象,生成拖拽信息,拖拽的数据会存在拖拽粘贴板上
  3. 拖拽放到 Destination,接收到拖拽信息,可以选择拒绝或接收,并进行一些操作
  4. Dragging Session 结束

需要处理的事情

操作拖拽的 NSView 需要实现 NSDraggingSource 协议,拖拽的目的地 NSView 需要实现 NSDraggingDestination 协议。另外可以通过 NSView 的拖拽相关 Extension 去注册支持拖拽的类型 NSPasteboard.PasteboardType。目前支持的类型有几种图片类型、字体、颜色、URL、fileURL 等。拖拽之后,从 NSDraggingInfo 中获取相关信息进行处理。

协议具体需要实现的方法比较简单,没有什么特别要讲的。

NSDraggingSource

func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation
optional func draggingSession(_ session: NSDraggingSession, willBeginAt screenPoint: NSPoint)
optional func draggingSession(_ session: NSDraggingSession, movedTo screenPoint: NSPoint)
optional func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation)
optional func ignoreModifierKeys(for session: NSDraggingSession) -> Bool

NSDraggingDestination

optional func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation
optional func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation
optional func draggingExited(_ sender: NSDraggingInfo?)
optional func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool
optional func performDragOperation(_ sender: NSDraggingInfo) -> Bool
optional func concludeDragOperation(_ sender: NSDraggingInfo?)
optional func draggingEnded(_ sender: NSDraggingInfo)
optional func wantsPeriodicDraggingUpdates() -> Bool
optional func updateDraggingItemsForDrag(_ sender: NSDraggingInfo?)

NSView 拖拽相关的 Extension

open func beginDraggingSession(with items: [NSDraggingItem], event: NSEvent, source: NSDraggingSource) -> NSDraggingSession
open var registeredDraggedTypes: [NSPasteboard.PasteboardType] { get }
open func registerForDraggedTypes(_ newTypes: [NSPasteboard.PasteboardType])
open func unregisterDraggedTypes()

Demo 功能实现

简单实现下功能。本文 Demo 详见文章末尾的链接。

readme.gif

功能拆解

  1. 需求
  • 基于提供的大尺寸图片,批量生成不同尺寸的 icon

好吧,需求总是简单一句话描述,大概所有程序猿都讨厌产品经理这么说哈哈。

  1. 设想用户操作
  • 设计生成 icon 大尺寸图片
  • 支持拖拽来指定该文件为生成的基准
  • 勾选需要的尺寸
  • 指定生成目录

实现

拖拽是从别的文件夹窗口拖拽过来的,所以不需要定义一个 NSView 去实现 NSDraggingSource 协议。

下面定义 DestinationView,NSView 类型,实现 NSDraggingDestination 协议。因为 NSView 默认扩展了该协议,所以不需要声明扩展。

定义 Delegate 代理,DestinationViewDelegate,通知外部。

protocol DestinationViewDelegate {
  func processImage(_ image: NSImage)
}

awakeFromNib 中注册支持的类型 NSPasteboard.PasteboardType.fileURL

registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])

然后实现 NSDraggingDestination 协议几个方法

draggingEntered 需要判断拖拽的内容是否接收,本 Demo 没有处理。

override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
    return .copy
}

可以进行以下处理:

let pasteBoard = sender.draggingPasteboard
let accept = NSImage(pasteboard: pasteBoard) != nil
return accept ? .copy : NSDragOperation()

The data represented by the image can be copied. 返回 .copy 是为了后面展示图片;

具体参考 NSDragoperation

draggingExited,当拖拽退出时,需要设置 needsDisplay 为 true。

needsDisplay,NSView 用于确定在显示之前是否需要重绘视图。

performDragOperation,判断是否图片,并处理。

下面是 DestinationView 完整的代码。

//  DestinationView.swift
import Cocoa

protocol DestinationViewDelegate {
  func processImage(_ image: NSImage)
}

class DestinationView: NSView {

  var delegate: DestinationViewDelegate?

  override func awakeFromNib() {
      registerForDraggedTypes([NSPasteboard.PasteboardType.fileURL])
  }

  var isReceivingDrag = false {
      didSet {
          needsDisplay = true
      }
  }

  override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
      return .copy
  }

  override func draggingExited(_ sender: NSDraggingInfo?) {
      isReceivingDrag = false
  }

  override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
      return true
  }

  override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {

      isReceivingDrag = false
      let pasteBoard = sender.draggingPasteboard
      guard let image = NSImage(pasteboard: pasteBoard) else {
          return false
      }
      delegate?.processImage(image)
      return true
  }
}

文件的读写权限

macOS开发,文件相关的组件有两个 NSOpenPanel 和 NSSavePanel。前者用于选择文件或者文件夹,后者用于保存单个文件。因为需要批量保存,NSSavePanel 无法实现,所以使用 Data 的 write 方法。

public func write(to url: URL, options: Data.WritingOptions = []) throws

文件读写,需要修改对应 Target 的 Capabilities 中的 App Sanbox - File Access 权限。

User Selected File 修改为 Read/Write 即可读写。

也可以关闭 App Sanbox 。

try? imageData?.write(to: path.appendingPathComponent("icon\(iconSize.rawValue).png"))

发布到 Mac AppStore 的应用,必须遵守沙盒约定。macOS APP 不需要上架,可以不开启 Sandbox 功能,随意访问 mac 上的文件。

NSView 设置问题

NSView 没有 backgroundColor 属性,修改背景色需要修改 layer 的。

backgroundView.layer?.backgroundColor = NSColor.white.cgColor

并且需要设置 wantsLayer 生效。

backgroundView.wantsLayer = true

包括设置圆角,也需要设置 wantsLayer。

backgroundView.layer?.cornerRadius = 10

关于 Retina 设备生成图片大小问题

public convenience init(size: NSSize, flipped drawingHandlerShouldBeCalledWithFlippedContext: Bool, drawingHandler: @escaping (NSRect) -> Bool)

在 Retina 设备上,NSImage 的初始化方法传递参数 size ,生成的图片的尺寸会是 size 的两倍。不了解,暂时先除以 2 解决。后面找到原因再处理下,如果了解原因欢迎给我留言哈。

总结

没什么好总结的哈哈。

本文Demo:https://github.com/sapphirezzz/AppIconReducer

-END-
欢迎到我的博客交流:https://zackzheng.info

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 文章来源:苹果官方文档 介绍拖放 Cocoa 让我们能够实现时髦高雅的拖放功能,在应用内部或者在应用之间。这个编程...
    张嘉夫阅读 3,483评论 2 52
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • Drag and Drop (Beta) With a single finger, a user can mov...
    肖建春阅读 561评论 0 0
  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 4,866评论 0 9
  • 翻译自“Auto Layout Guide”。 1 入门 1.1 理解自动布局 自动布局根据视图层级结构中视图上的...
    lakerszhy阅读 3,472评论 3 26