Implement the proposed solution to do drag and drop in this thread:
SwiftUI | Using onDrag and onDrop to reorder Items within one single LazyGrid?
The problem I have is that I want to play a vibration when the drag starts and I don't get it.
I did the following:
.onDrag(){
let impact = UIImpactFeedbackGenerator(style: .light)
impact.impactOccurred()
self.dragging = module
return NSItemProvider(object: String(module.id) as NSString)
}
But if I drop the item on itself (to cancel or by accident). The next time I try to drag it, the onDrag() method is not executed.
How could I fix it?
Related
On the iPad, I'm trying to build a simple app that continuously tracks the pointer's coordinates as it moves across the screen (via external mouse/trackpad). Basically something like this JavaScript example # 4:13 except in a SwiftUI view on the iPad.
MacOS has NSEvent.mouseLocation, but it doesn't seem like there's an iPadOS counterpart. Every resource I've come across online necessarily associates coordinates with a gesture (rotation, pinch, drag, click, etc.) with no way to respond to only cursor movement. This leads me to believe that the solution for pure pointer movement is likely independent of the Gesture protocol.
I was able to get halfway there after modifying code from this SO post. My code below displays updated mouse coordinates so long as the pointer is dragging (i.e., while at least one button is pressed).
import SwiftUI
struct ContentView: View {
#State private var pt = CGPoint()
#State private var txt = "init"
var body: some View {
let myGesture = DragGesture(
minimumDistance: 0,
coordinateSpace: .local
)
.onChanged {
self.pt = $0.location
self.txt = "x: \(self.pt.x), y: \(self.pt.y)"
}
// Spacers needed to make the VStack occupy the whole screen
return VStack {
Spacer()
Text(self.txt)
Spacer()
}
.frame(width: 1000, height: 1000)
.border(Color.green)
.contentShape(Rectangle()) // Make the entire VStack tappabable, otherwise, only the area with text generates a gesture
.gesture(myGesture) // Add the gesture to the Vstack
}
}
Now, how do I achieve this effect without needing to drag? Could there be some way to do this with the help of objective-C if a pure Swift solution isn't possible?
Thanks
Edit:
The WWDC video covers this very topic:
Handle trackpad and mouse input
Add SupportsIndirectInputEvents to your Info.plist
From the video:
It is required in order to get the new touch type indirect pointer and
EventType.transform.
Existing projects do not have this key set and will need to add it. Starting with iOS 14 and macOS Big Sur SDKs, new UIKit and SwiftUI
projects will have this value set to "true."
In addition you will use UIPointerInteraction. This tutorial shows you step by step including custom cursors:
https://pspdfkit.com/blog/2020/supporting-pointer-interactions/
I am making drag & drop lazyVGrid.
i did reference this answer
SwiftUI | Using onDrag and onDrop to reorder Items within one single LazyGrid?
when drag move another item position it working well
but when I long press and not move and drop itself
self.dragging not set to nil
how can I fix it??
I would try to do like this.
.gesture(DragGesture()
.onChanged({ value in
if value.x == 0 && value.y == 0 {
self.dragging = nil
}
})
)
In Reality Composer I created MyScene.rcproject, in MyScene I created a panel and its spin animation, the action sequence begins when its notification is posted from code.
When it's come to trigger the animation from MyScene.rcproject it works like I expect:
self.sceneAnchor = try MyScene.loadMyScene()
sceneAnchor.generateCollisionShapes(recursive: true)
sceneAnchor.notifications.spin.post()
But when I export MyScene.rcproject file to MyScene.reality and I implement it into another app project:
panelEntity = try? ModelEntity.load(named: "MyScene")
sceneAnchor.addChild(panelEntity)
I can't find any method to trigger the spin animation. Is it possible to trigger entity's animation, inside MyScene.reality file?
In RealityKit 2.0, if using .reality file instead of .rcproject, Notifications class is inaccessible.
public private(set) lazy var notifications = A.Scene.Notifications(root: self))
Having a SwiftUI slider.
The user keeps on dragging it around.
While dragging, the user also presses the Option key.
I want to change the user interface based on the keyboard modifier flag change (e.g. the Option key). However, it seems the main event loop is blocked while dragging the slider or even when having a mouse button pressed.
When using the NSEvent.addLocalMonitorForEvents(mathing:handler) to get notified of the Option keypress, the handler does not even get called while the user drags the slider.
Is there any way I can achieve that?
I would also love to understand why the problem exists in the first place.
Eventually, I ended up using a timer block checking and publishing changes of the keyboard modifier flags.
import Cocoa
import Combine
/**
Publishers for keyboard events.
The modifiers are checked regularly by a Timer running on the Main `RunLoop` in `.common` mode
to ensure the modifiers are detected even when having mouse button pressed down
(which normally blocks the run loops on the main thread which are not in `common` mode).
The `NSEvent.addLocalMonitorForEvents` cannot be used because the handler is not executed
for nested-tracking loops, such as control tracking (e.g. when a mouse button is down).
*/
public class KeyboardEvents {
/// Broadcasts keyboard flag changes (e.g. keys like Option, Command, Control, Shift, etc.)
public let modifierFlagsPublisher: AnyPublisher<NSEvent.ModifierFlags?, Never>
private let modifierFlagsSubject = PassthroughSubject<NSEvent.ModifierFlags?, Never>()
private var timer: Timer!
public init() {
modifierFlagsPublisher = modifierFlagsSubject.removeDuplicates().eraseToAnyPublisher()
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [unowned self] _ in
modifierFlagsSubject.send(NSApp.currentEvent?.modifierFlags)
}
RunLoop.current.add(timer, forMode: .common)
}
deinit {
timer.invalidate()
}
}
I have a view that contains some objects like this (the green area is my view):
Every time I press "item" a new color object gets added to this row of views, I want the "row" button to copy this row as it is and place it above this row when pressed, so I have two (or multiple) rows on top of each other. I made the action for row button like this:
let hc = contentView1
hc?.setFrameOrigin(NSPoint(x: 0, y: (positionY*50)))
positionY += 1
contentView2?.addSubview(hc!)
but every time I press that button, the same row just moves and does not copy, like this:
What am I doing wrong?
A view can have one superview and each time you add a view to a superview, it is removed from its previous superview. NSView doesn't implement the NSCopying protocol so you can't copy a view by calling copy. Search SO for 'copy NSView'.
It looks like the issue is that you are creating a new view that is equal to the view you want to copy. However, NSViews are reference types, not value types. So editing one view will edit the other.
NSView luckily implements the NSCoding protocol, so you can duplicate the view like this:
let archivedView = NSKeyedArchiver.archivedDataWithRootObject(contentView1)
let hc = NSKeyedUnarchiver.unarchiveObjectWithData(archivedView)
And then do the rest:
hc?.setFrameOrigin(NSPoint(x: 0, y: (positionY*50)))
positionY += 1
contentView2?.addSubview(hc!)