如何让图层支持 NSView 写入剪贴板中的 PDF
How do I get a layer backed NSView to write to PDF in the clipboard
我正在使用带 CGImage
的图层支持视图,并想使用 NSView.writePDF(inside:to)
API 将视图图像复制到剪贴板。该视图具有我希望包含在 PDF 中的子视图。
我创建了一个 Xcode 游乐场来说明这个问题。
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
context.setFillColor(cgFillColor)
context.fill(dirtyRect)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
@objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
@objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: @escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "B-019969.jpg") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.yellow
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
有没有办法确保图层包含在 PDF 输出中?
好的,我刚刚弄明白了 - 基本上你必须实现 draw(_ dirtyRect)
函数,该函数在打印期间或调用 NSView.writePDF() 时被调用。
请注意,在某些情况下不会调用 draw() 函数 - 例如使用鼠标选择项目只会导致调用 updateLayer() 方法。
这是更新后的游乐场,希望这能为其他人节省一些时间。
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
var image: CGImage?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
// NB THIS PART !!
self.layer?.render(in:context)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
@objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
@objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: @escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "Sample Image.png") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.green
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override func draw(_ dirtyRect: NSRect) {
print("ShelfView.draw() called")
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
if let image = self.storeLayoutCGImageRef {
context.draw(image, in: self.bounds)
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
print("updateLayer() called")
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
我正在使用带 CGImage
的图层支持视图,并想使用 NSView.writePDF(inside:to)
API 将视图图像复制到剪贴板。该视图具有我希望包含在 PDF 中的子视图。
我创建了一个 Xcode 游乐场来说明这个问题。
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
context.setFillColor(cgFillColor)
context.fill(dirtyRect)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
@objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
@objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: @escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "B-019969.jpg") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.yellow
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v
有没有办法确保图层包含在 PDF 输出中?
好的,我刚刚弄明白了 - 基本上你必须实现 draw(_ dirtyRect)
函数,该函数在打印期间或调用 NSView.writePDF() 时被调用。
请注意,在某些情况下不会调用 draw() 函数 - 例如使用鼠标选择项目只会导致调用 updateLayer() 方法。
这是更新后的游乐场,希望这能为其他人节省一些时间。
//: A Cocoa based Playground to test NSView.writePDF()
import AppKit
import PlaygroundSupport
class BaseView: NSView {
var isSelected: Bool = false {
didSet {
self.needsDisplay = true
}
}
var fillColor = NSColor.yellow
var cgFillColor: CGColor {
return self.isSelected ? fillColor.cgColor : fillColor.withAlphaComponent(0.5).cgColor
}
var storeLayoutCGImageRef: CGImage? {
didSet {
self.layer?.contents = storeLayoutCGImageRef
}
}
var image: CGImage?
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
// NB THIS PART !!
self.layer?.render(in:context)
}
override func mouseDown(with theEvent: NSEvent) {
self.isSelected = !self.isSelected
}
@objc func copyImage(_ sender: Any){
let pb = NSPasteboard.general
pb.declareTypes([.pdf], owner: self)
self.writePDF(inside: self.bounds, to: pb)
}
@objc func doNothing(_ sender: Any){
}
// Load image in the background
func loadImage(completion: @escaping ()->Void){
DispatchQueue.global().async {
if let image = NSImage(named: "Sample Image.png") {
// Lets use double the size ?
var rect = CGRect(x: 0, y: 0, width: image.size.width*3.0, height: image.size.height*3.0)
let cgImage = image.cgImage(forProposedRect: &rect, context: nil, hints: nil)
DispatchQueue.main.async {
self.storeLayoutCGImageRef = cgImage
completion()
}
return
}
DispatchQueue.main.async {
//self.storeLayoutImage = nil
self.storeLayoutCGImageRef = nil
completion()
}
}
}
}
class ModuleView: BaseView {
let color = NSColor.purple
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy module", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
class ShelfView: BaseView {
let color = NSColor.green
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.fillColor = color
self.loadImage {
print("Image loaded")
}
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.fillColor = color
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy shelf", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
override func draw(_ dirtyRect: NSRect) {
print("ShelfView.draw() called")
super.draw(dirtyRect)
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
if let image = self.storeLayoutCGImageRef {
context.draw(image, in: self.bounds)
}
}
override var wantsUpdateLayer: Bool {
return true
}
override func updateLayer() {
print("updateLayer() called")
self.layer?.backgroundColor = self.cgFillColor
self.layer?.contents = self.storeLayoutCGImageRef
}
}
class view: BaseView
{
var module: ModuleView?
let color = NSColor.cyan
override init(frame: NSRect)
{
super.init(frame: frame)
self.fillColor = color
self.addModules()
self.addShelves()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addModules(){
let module = ModuleView()
module.frame = NSRect(x: 10.0, y: 10.0, width: 180.0, height: 180.0)
self.module = module
self.addSubview(module)
}
func addShelves(){
let shelf = ShelfView()
shelf.frame = NSRect(x: 10.0, y: 10.0, width:160.0, height: 160.0)
self.module?.addSubview(shelf)
}
override func menu(for event: NSEvent) -> NSMenu? {
if !isSelected {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
return menu
} else {
// Display popup menu
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Copy fixture", action: #selector(copyImage(_:)), keyEquivalent: ""))
menu.addItem(NSMenuItem(title: "Do Something", action: #selector(doNothing(_:)), keyEquivalent: ""))
return menu
}
}
}
var v = view(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = v