如何在 SwiftUI (macOS) 中拖放未捆绑的图像
Posted
技术标签:
【中文标题】如何在 SwiftUI (macOS) 中拖放未捆绑的图像【英文标题】:How to drag and drop images not in bundle in SwiftUI (macOS) 【发布时间】:2020-05-09 23:38:14 【问题描述】:我正在尝试在 macOS 上的 SwiftUI 中实现拖放,我可以在其中以编程方式生成图像并将它们作为可拖动项显示在视图中,或者出于相同目的从 Assets.xcassets 加载图像。我的代码基于this。我试图参考this question,但我无法让它工作。
我的图像显示正常,但由于我引用的是图像本身,因此我无法为拖放 API 返回 URL(见下文):
//Value of type 'ContentView.DragableImage' has no member 'url' so I cannot use this
.onDrag return NSItemProvider(object: self.url as NSURL)
这里是代码。我已经通过代码中的 cmets 指出了一些事情:
import SwiftUI
let img1url = Bundle.main.url(forResource: "grapes", withExtension: "png") // < -- CAN pass this in because it is by url
let img2url = Bundle.main.url(forResource: "banana", withExtension: "png")
let img3url = Bundle.main.url(forResource: "peach", withExtension: "png")
let img4url = Bundle.main.url(forResource: "kiwi", withExtension: "png")
struct ContentView: View
var body: some View
HStack
VStack
//DragableImage(url: img1url!)
//DragableImage(url: img3url!)
DragableImage()
DragableImage()
VStack
//DragableImage(url: img2url!)
// DragableImage(url: img4url!)
DragableImage()
DragableImage()
DroppableArea()
.padding(40)
struct DragableImage: View
//let url: URL
var body: some View
Image("grapes") //<--- Takes in image without url fine
//Image(nsImage: NSImage(byReferencing: url)) //<--- Taking in image by URL (I don't want that)
.resizable()
.frame(width: 150, height: 150)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.padding(2)
.overlay(Circle().strokeBorder(Color.black.opacity(0.1)))
.shadow(radius: 3)
.padding(4)
.onDrag return NSItemProvider(object: self.url as NSURL) //<--- MAIN ISSUE: "Value of type 'ContentView.DragableImage' has no member 'url'" (there is now no URL to reference the image by in the return)
struct DroppableArea: View
@State private var imageUrls: [Int: URL] = [:]
@State private var active = 0
var body: some View
let dropDelegate = MyDropDelegate(imageUrls: $imageUrls, active: $active)
return VStack
HStack
GridCell(active: self.active == 1, url: imageUrls[1])
GridCell(active: self.active == 3, url: imageUrls[3])
HStack
GridCell(active: self.active == 2, url: imageUrls[2])
GridCell(active: self.active == 4, url: imageUrls[4])
.background(Rectangle().fill(Color.gray))
.frame(width: 300, height: 300)
.onDrop(of: ["public.file-url"], delegate: dropDelegate)
struct GridCell: View
let active: Bool
let url: URL?
var body: some View
let img = Image(nsImage: url != nil ? NSImage(byReferencing: url!) : NSImage())
.resizable()
.frame(width: 150, height: 150)
return Rectangle()
.fill(self.active ? Color.green : Color.clear)
.frame(width: 150, height: 150)
.overlay(img)
struct MyDropDelegate: DropDelegate
@Binding var imageUrls: [Int: URL]
@Binding var active: Int
func validateDrop(info: DropInfo) -> Bool
return info.hasItemsConforming(to: ["public.file-url"])
func dropEntered(info: DropInfo)
NSSound(named: "Morse")?.play()
func performDrop(info: DropInfo) -> Bool
NSSound(named: "Submarine")?.play()
let gridPosition = getGridPosition(location: info.location)
self.active = gridPosition
if let item = info.itemProviders(for: ["public.file-url"]).first
item.loadItem(forTypeIdentifier: "public.file-url", options: nil) (urlData, error) in
DispatchQueue.main.async
if let urlData = urlData as? Data
self.imageUrls[gridPosition] = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
return true
else
return false
func dropUpdated(info: DropInfo) -> DropProposal?
self.active = getGridPosition(location: info.location)
return nil
func dropExited(info: DropInfo)
self.active = 0
func getGridPosition(location: CGPoint) -> Int
if location.x > 150 && location.y > 150
return 4
else if location.x > 150 && location.y < 150
return 3
else if location.x < 150 && location.y > 150
return 2
else if location.x < 150 && location.y < 150
return 1
else
return 0
我还尝试了以下方法并取得了更大的成功。它在图像拖动时突出显示,但在放置时不会显示:
import SwiftUI
let image1 = NSImage(named: "green")!
let image2 = NSImage(named: "blue")!
struct ContentView: View
var body: some View
HStack
VStack
DragableImage(image: image1)
DragableImage(image: image2)
VStack
DragableImage(image: image1)
DragableImage(image: image2)
DroppableArea()
.padding(40)
struct DragableImage: View
@State var image: NSImage
@State private var dragOver = false
var body: some View
Image(nsImage: image)
.onDrop(of: ["public.file-url"], isTargeted: $dragOver) providers -> Bool in
providers.first?.loadDataRepresentation(forTypeIdentifier: "public.file-url", completionHandler: (data, error) in
if let data = data, let path = NSString(data: data, encoding: 4), let url = URL(string: path as String)
let imageLocal = NSImage(contentsOf: url)
DispatchQueue.main.async
self.image = imageLocal!
)
return true
.onDrag
let data = self.image.tiffRepresentation
let provider = NSItemProvider(item: data as NSSecureCoding?, typeIdentifier: kUTTypeTIFF as String)
provider.previewImageHandler = (handler, _, _) -> Void in
handler?(data as NSSecureCoding?, nil)
return provider
.border(dragOver ? Color.red : Color.clear)
struct DroppableArea: View
@State private var imageUrls: [Int: URL] = [:]
@State private var active = 0
var body: some View
let dropDelegate = MyDropDelegate(imageUrls: $imageUrls, active: $active)
return VStack
HStack
GridCell(active: self.active == 1, url: imageUrls[1])
GridCell(active: self.active == 3, url: imageUrls[3])
HStack
GridCell(active: self.active == 2, url: imageUrls[2])
GridCell(active: self.active == 4, url: imageUrls[4])
.background(Rectangle().fill(Color.gray))
.frame(width: 300, height: 300)
.onDrop(of: ["public.file-url"], delegate: dropDelegate)
struct GridCell: View
let active: Bool
let url: URL?
var body: some View
let img = Image(nsImage: url != nil ? NSImage(byReferencing: url!) : NSImage())
.resizable()
.frame(width: 150, height: 150)
return Rectangle()
.fill(self.active ? Color.green : Color.clear)
.frame(width: 150, height: 150)
.overlay(img)
struct MyDropDelegate: DropDelegate
@Binding var imageUrls: [Int: URL]
@Binding var active: Int
func validateDrop(info: DropInfo) -> Bool
return info.hasItemsConforming(to: ["public.file-url"])
func dropEntered(info: DropInfo)
NSSound(named: "Morse")?.play()
func performDrop(info: DropInfo) -> Bool
NSSound(named: "Submarine")?.play()
let gridPosition = getGridPosition(location: info.location)
self.active = gridPosition
if let item = info.itemProviders(for: ["public.file-url"]).first
item.loadItem(forTypeIdentifier: "public.file-url", options: nil) (urlData, error) in
DispatchQueue.main.async
if let urlData = urlData as? Data
self.imageUrls[gridPosition] = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL
return true
else
return false
func dropUpdated(info: DropInfo) -> DropProposal?
self.active = getGridPosition(location: info.location)
return nil
func dropExited(info: DropInfo)
self.active = 0
func getGridPosition(location: CGPoint) -> Int
if location.x > 150 && location.y > 150
return 4
else if location.x > 150 && location.y < 150
return 3
else if location.x < 150 && location.y > 150
return 2
else if location.x < 150 && location.y < 150
return 1
else
return 0
【问题讨论】:
【参考方案1】:和参考题几乎一样,关键是用NSImage
作为模型,而不是直接使用Image
,才能有能力访问图像数据以使用NSItemProvider
:
struct DragableImage: View
var image = NSImage(named: "grapes")
var body: some View
Image(nsImage: image ?? NSImage())
.resizable()
.frame(width: 150, height: 150)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.padding(2)
.overlay(Circle().strokeBorder(Color.black.opacity(0.1)))
.shadow(radius: 3)
.padding(4)
.onDrag
let data = self.image?.tiffRepresentation
let provider = NSItemProvider(item: data as NSSecureCoding?, typeIdentifier: kUTTypeTIFF as String)
provider.previewImageHandler = (handler, _, _) -> Void in
handler?(data as NSSecureCoding?, nil)
return provider
【讨论】:
唯一的问题是可拖动的图像(绿色和蓝色)不会在放置时显示(它甚至不会播放来自 dropEntered 或 performDrop 的声音 (图像永远不会被添加到 DroppableArea)以上是关于如何在 SwiftUI (macOS) 中拖放未捆绑的图像的主要内容,如果未能解决你的问题,请参考以下文章