Posts

Post not yet marked as solved
0 Replies
476 Views
Hi and thank you in advance for the support. I’m working on a project that allows users to drag and drop files into and out of my app using the Drag and Drop API. From my understanding, dropping a file into the app requires the file to be an NSItemProvider to be eligible for a drop - like this: func onDrop(of: [UTType], is Targeted: Binding<Bool>?, perform: ([NSItemProvider]) -> Bool) -> some View and dragging a file out of the app requires the same: func onDrag (() -> NSItemProvider) -> some View The only success I’ve had with this is actually specifying the subtype of NSItemProvider the app will be receiving via drop, or dragging away - like this: /*Example view with drop modifier*/ ScrollView { Text(“Dropped text”) .onDrag { handleDragAway(for: text) } } .onDrop(of: [UTType.text.identifier], isTargeted: nil) { providers in handleDrop(providers: providers) } /*Handle drop of item NSItemProvider conforming to plain text*/ func handleDropInto(providers: [NSItemProvider]) -> Bool { var didHandle = false for provider in providers { if provider.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { _ = provider.loadObject(ofClass: String.self) { string, _ in DispatchQueue.main.async { if let string = string { self.handlePlainText(text: string) didHandle = true } } } } else { didHandle = false } return didHandle } } /*Handle dragging the item away*/ func handleDragAway(for text: String) -> NSItemProvider { return NSItemProvider(object: text as NSString) } Essentially this code, albeit incomplete, gets the idea across. Drag text into the view, display the text, drag it out. Now, all the documentation I can find says you can rinse and repeat for basically any file type (with some processing) that conforms to NSItemProvider. Here is my question: what if I don’t want to specify file type - what if I just want to tell the app, the user is dragging in some file that conforms to NSItemProvider, don’t worry about its type. Display “File” to show the user they have successfully dropped a file into the view, but don’t do any with the file (this could be implemented later). Then, the user can drag that file away, conforming to NSItemProvider, and wherever they drag it can deal with its file type. Here is an implementation of this idea: import SwiftUI import UniformTypeIdentifiers struct BoardItem { let id: UUID let provider: NSItemProvider } struct BoardItemView: View { var body: some View { Text("File") .frame(width: 100, height: 100) .border(Color.black) } } struct BoardView: View { @State private var boardItems: [BoardItem] = [] var body: some View { ScrollView { ForEach(boardItems, id: \.id) { item in BoardItemView() .onDrag { return item.provider } } } .onDrop(of: [UTType.item.identifier], isTargeted: nil) { providers in providers.forEach { provider in let newItem = BoardItem(id: UUID(), provider: provider) DispatchQueue.main.async { boardItems.append(newItem) } } return true } .frame(maxWidth: .infinity, maxHeight: .infinity) } } The code compiles and runs easily on iPadOS or visionOS. The user can drop anything into the view, “File” appears on screen, and the user can even grab the text (that represents the file). However, dragging the object into a new app does not work. There is no green “+” element to verify the object is droppable anywhere, and attempting to drop it anywhere results in a red console message in Xcode saying Cannot retrieve a strong reference to PBItemCollection. Does anyone know how to fix the functionality and the error? I appreciate you engaging in a long post, thank you!
Posted
by jklawlor.
Last updated
.
Post not yet marked as solved
10 Replies
1.5k Views
I have run into this SwiftData issue in multiple projects and have been able to replicate it by building off of the default SwiftData launch project. The original Item class: class Item { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } } New MyItem class to replicate the error. Notice I nest an Item object inside MyItem: class MyItem { var name: String var item: Item init(name: String, item: Item) { self.name = name self.item = item } } I then build off of the default view for a SwiftData project. When the '+' button is pressed, a new list item for both Item and MyItem should appear in their appropriate sections. @Environment(\.modelContext) private var modelContext @Query private var items: [Item] @Query private var myItems: [MyItem] var body: some View { NavigationSplitView { List { Section("All Items") { ForEach(items) { item in NavigationLink { Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } label: { Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } } Section("My Items") { ForEach(myItems) { myItem in NavigationLink { Text("Item at \(myItem.item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") } label: { HStack { Text(myItem.name) Spacer() Text(myItem.item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) } } } } } .toolbar { ToolbarItem { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } detail: { Text("Select an item") } } private func addItem() { withAnimation { let newItem = Item(timestamp: Date()) modelContext.insert(newItem) let newMyItem = MyItem(name: "Test", item: newItem) modelContext.insert(newMyItem) } } } The app crashes and I get the following error when I attempt to click the '+' button (which should create a new Item and MyItem in the modelContext: Thread 1: "Illegal attempt to establish a relationship 'item' between objects in different contexts (source = <NSManagedObject: 0x600002166940> (entity: MyItem; id: 0x600000298240 x-coredata:///MyItem/t2D4951EB-0D2F-44B1-AF8C-5A1BB11659F53; data: {\n item = nil;\n name = Test;\n}) , destination = <NSManagedObject: 0x600002174000> (entity: Item; id: 0x600000232440 x-coredata:///Item/t2D4951EB-0D2F-44B1-AF8C-5A1BB11659F52; data: {\n timestamp = "2023-10-04 18:21:21 +0000";\n}))" Can anyone help me understand the new SwiftData framework in this regard? I am still new to SwiftUI.
Posted
by jklawlor.
Last updated
.
Post not yet marked as solved
0 Replies
296 Views
Hello! I'm having a little issue with the onCommit feature for TextField. Basically what I am trying to do is create a List with a Section header and a TextField inside, nothing crazy. My current code looks something like this: struct FormView: View { @State private var myContent: String = "" var body: some View { Form { Section(header: Text("Name")) { TextField("Contents", text: $myContent, axis: .vertical, onCommit: { // Perform action on commit updateData() }) } } } private func updateData() { // Perform the data update here } } However, I am getting an error that says Extra argument 'onCommit' in call I'm able to get rid of the error by deleting either the axis: .vertical or the onCommit: {...}. I do want both of these functionalities, though, as the axis: .vertical essentially wraps the text to a new line when needed and the onCommit: {...} updates my app database using UserDefaults. Is there a way to create a TextField within a List and Section that has a $binding variable, axis, and onCommit functionality or am I dreaming?
Posted
by jklawlor.
Last updated
.
Post not yet marked as solved
2 Replies
301 Views
I'm still a beginner in SwiftUI so there is probably a better way to do this, but basically what I want to do is have a form of code length management where I have all these different buttons in my app that do the same thing. So naturally I want to create a method to call in each button action so I don't have to copy and paste the action every time. This is the method I created: class NumberButtonLogic {     var operation : Int     var tempOp    : Int     var output    : String     var temp      : String     var buttonNum : Int     init(operation: Int, tempOp: Int, output: String, temp: String, buttonNum: Int) {         self.operation = operation         self.tempOp    = tempOp         self.output    = output         self.temp      = temp         self.buttonNum = buttonNum     }     func runLogic() {         if operation != 0 {             if output == "0." {             } else {                 output = ""             }             tempOp = operation             operation = 0         }         if output == "0" || output == "0.0" {             if temp != "" {             } else {                 output = ""             }         }         output += String(buttonNum)     } } Don't worry too much about the functionality of the runLogic() function, it's a mess, I know lol. But I basically want to have the different vars at the top be changed in runLogic(). From there, I go to a separate View area and create a new button: @State private var output: String = "0" @State private var operation: Int = 0 @State private var temp: String = "" @State private var tempOp: Int = 0 var numButton1 : NumberButtonLogic {         NumberButtonLogic(operation: operation, tempOp: tempOp, output: output, temp: temp, buttonNum: 1)     } Button { numButton1.runLogic() } label: { Text("1") }.buttonStyle(numberStyle()) Again don't worry about the numberStyle() style, it is working in the preview, so I don't think that's the issue with my code. Basically what I want to do is display the number "1" on the screen when the button is pressed (but format it differently depending on what is already there). Also everything in the second code block I gave is in the struct ContentView : View {}. Right now, when I press the button, I don't get any output, and the += modifier for output doesn't add the number "1" to the end of the string. However, if I put the code from the runLogic() function directly inside the button action, it runs fine. I hope that makes sense and thanks for your help!
Posted
by jklawlor.
Last updated
.