首页 > 解决方案 > 如何在 SwiftUI (macOS) 中拖放未捆绑的图像

问题描述

我正在尝试在 macOS 上的 SwiftUI 中实现拖放,我可以在其中以编程方式生成图像并将它们作为可拖动项目显示在视图中,或者出于相同目的从 Assets.xcassets 加载图像。我的代码基于。我试图参考这个问题,但我无法让它发挥作用。

我的图像显示正常,但由于我引用的是图像本身,因此我无法为拖放 API 返回 URL(见下文):

//Value of type 'ContentView.DragableImage' has no member 'url' so I cannot use this
.onDrag { return NSItemProvider(object: self.url as NSURL) }

这是代码。我通过代码中的注释指出了一些事情:

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
            }
        }
    }
}

标签: swiftui

解决方案


它与参考问题几乎相同,关键是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
            }
    }
}

推荐阅读