I would like to create a gesture which would require LongTapGesture before one of other two gestures (DragGesture and MagnificationGesture) is possible. It works fine for just dragging, but if you try to magnify it soon ends in a hangup state - looks like magnification never ends.
What would be the right way to do it?
Here's the code:
struct DraggableCircle: View {
enum DragState {
case inactive
case pressing
case draggingOrMagnifying(translation: CGSize, magnification: CGFloat)
var translation: CGSize {
switch self {
case .inactive, .pressing:
return .zero
case .draggingOrMagnifying(let translation, _):
return translation
}
}
var magnification: CGFloat {
switch self {
case .inactive, .pressing:
return 1
case .draggingOrMagnifying(_, let magnification):
return magnification
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .pressing, .draggingOrMagnifying:
return true
}
}
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .draggingOrMagnifying:
return true
}
}
}
#GestureState var dragState = DragState.inactive
#State var viewState = CGSize.zero
#State var magState = CGFloat(1)
var body: some View {
let minimumLongPressDuration = 0.5
let longPressDrag = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture().exclusively(before: MagnificationGesture()))
.updating($dragState) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let exclusiveGesture):
switch exclusiveGesture {
case .first(let drag):
state = .draggingOrMagnifying(translation: drag.translation, magnification: 1)
case .second(let mag):
state = .draggingOrMagnifying(translation: .zero, magnification: mag)
case .none:
state = .draggingOrMagnifying(translation: .zero, magnification: 1)
}
print(state)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let gest?) = value else { return }
switch gest {
case .first(let drag):
self.viewState.width += drag.translation.width
self.viewState.height += drag.translation.height
case .second(let mag):
self.magState = magState * mag
}
}
return Circle()
.fill(Color.blue)
.overlay(dragState.isDragging ? Circle().stroke(Color.white, lineWidth: 2) : nil)
.frame(width: clipToBounds(100 * magState * dragState.magnification), height: clipToBounds(100 * magState * dragState.magnification), alignment: .center)
.offset(
x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.animation(nil)
.shadow(radius: dragState.isActive ? 8 : 0)
.animation(.linear(duration: minimumLongPressDuration))
.gesture(longPressDrag)
}
func clipToBounds(_ m: CGFloat) -> CGFloat {
if abs(m) > UIScreen.main.bounds.height {
return UIScreen.main.bounds.height
} else {
return abs(m)
}
}
}
Related
I'm using apple' approach to handle AR in sceneKit, and here I use this method to scale the object, it works fine when all the nodes of the object merged into one node, but because of the object's animations, I can't make all into one node. So Need to change this method differently that I can scale all the nodes together.
Can anyone help me on that? Thanks
var trackedObject: VirtualObject? {
didSet {
guard trackedObject != nil else { return }
selectedObject = trackedObject
}
}
#objc func pinched(_ gesture :UIPinchGestureRecognizer) {
switch gesture.state {
case .began:
gesture.scale = isScaledChanged ? CGFloat(trackedObject?.objectScale.x ?? 1) : CGFloat(1)
case .changed:
var newScale: SCNVector3
if gesture.scale < 0.5 {
newScale = SCNVector3(x: 0.5, y: 0.5, z: 0.5)
} else if gesture.scale > 3 {
newScale = SCNVector3(3, 3, 3)
} else {
newScale = SCNVector3(gesture.scale, gesture.scale, gesture.scale)
}
isScaledChanged = true
trackedObject?.objectScale = newScale
default:
break
}
}
and in the VirtualObject class
var objectScale: SCNVector3 {
get {
return childNodes.first!.scale
}
set (newValue) {
childNodes.first!.scale = newValue
}
}
I did something like that, but didn't work well
var objectScale: SCNVector3 {
get {
for node in childNodes {
return node.scale
}
return childNodes.first!.scale
}
set (newValue) {
for node in childNodes {
node.scale = newValue
}
}
}
Background
In this learning app, I've followed and excellent tutorial from Hacking with Swift on generating a wave-like animation. I've modified this app further adding some functionalities:
Providing Start/Stop mechanism for the wave animation
Perpetually generating random numbers for the duration of the animation
Modifying animation if an "interesting" number is found. Initially, I've implemented logic that defines even numbers as interesting but that could be easily changes to flag prime numbers, etc.
Problem
After stopping the animation does not "run" again. This is demonstrated in the gif below.
After stopping the animation does not restart.
Code
//
// ContentView.swift
// WaveExample
//
// Created by Konrad on 28/07/2021.
// Original tutorial: https://www.hackingwithswift.com/plus/custom-swiftui-components/creating-a-waveview-to-draw-smooth-waveforms
//
import SwiftUI
/**
Creates wave shape object
- Parameter strength: How tall the wave should be
- Parameter frequency: How densly the wave should be packed
- returns: Shape
*/
struct Wave: Shape {
// Basic wave characteristics
var strength: Double // Height
var frequency: Double // Number of hills
var phase: Double // Offsets the wave, can be used to animate the view
// Required to define that animation relates to moving the wave from left to right
var animatableData: Double {
get { phase }
set { self.phase = newValue }
}
// Path drawing function
func path(in rect: CGRect) -> Path {
let path = UIBezierPath()
// Basic waveline characteristics
let width = Double(rect.width)
let height = Double(rect.height)
let midWidth = width / 2
let midHeight = height / 2
let wavelength = width / frequency
let oneOverMidWidth = 1 / midWidth
// Path characteristics
path.move(to: CGPoint(x: 0, y: midHeight))
// By determines the nmber of calculations, can be decreased to run faster
for xPosition in stride(from: 0, through: width, by: 1) {
let relativeX = xPosition / wavelength // How far we are from the start point
let distanceFromMidWidth = xPosition - midWidth // Distance from the middle of the space
let normalDistance = distanceFromMidWidth * oneOverMidWidth // Get values from -1 to 1, normalize
// let parabola = normalDistance // Small waves in the middle
let parabola = -(normalDistance * normalDistance) + 1 // Big wave in the middle
let sine = sin(relativeX + phase) // Offset based on phase
let yPosition = parabola * strength * sine + midHeight // Moving this halfway
path.addLine(to: CGPoint(x: xPosition, y: yPosition))
}
return Path(path.cgPath)
}
}
struct Line: Shape {
func path(in rect: CGRect) -> Path {
// Positioning
let midHeight = rect.height / 2
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: midHeight))
path.addLine(to: CGPoint(x: rect.width, y: midHeight))
return Path(path.cgPath)
}
}
struct ContentView: View {
#State private var phase = 0.0 // Used to animate the wave
#State private var waveStrength: Double = 10.0 // How tall, change for interesting numbers
#State private var waveFrequency: Double = 10.0 // How frequent, change for interesting numbers
#State var isAnimating: Bool = false // Currently running animation
#State private var randNum: Int16 = 0 // Random number to keep generating while animating
#State private var isNumberInteresting: Bool = false // Will take 'true' of the random number has some interesting properties
// Timer publisher reflecting frequent animation changes
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
// Stop timer
func stopTimer() {
self.timer.upstream.connect().cancel()
}
// Start timer
func startTimer() {
self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
}
// Check if number is interesting
func checkNumber(num: Int16) -> Bool {
var isInteresting: Bool = false
if num % 2 == 0 {
isInteresting.toggle()
}
return isInteresting
}
var body: some View {
VStack {
if self.isAnimating {
VStack {
Button("Stop") {
self.isAnimating = false
stopTimer()
}
.font(.title)
.foregroundColor(Color(.blue))
Text("Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))")
.onReceive(timer, perform: { _ in
randNum = Int16.random(in: 0..<Int16.max)
isNumberInteresting = checkNumber(num: randNum)
})
}
} else {
Button("Start") {
self.isAnimating = true
startTimer()
}
.font(.title)
.foregroundColor(Color(.red))
}
if self.isAnimating {
// Animation
ZStack {
ForEach(0..<10) { waveIteration in
Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
.stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
.offset(y: CGFloat(waveIteration) * 10)
}
}
.onReceive(timer) { _ in
// withAnimation requires info on how to animate
withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
self.phase = .pi * 2 // 180 degrees of sine being calculated
if isNumberInteresting {
waveFrequency = 50.0
waveStrength = 50.0
} else {
waveFrequency = 10.0
waveStrength = 10.0
}
}
}
.frame(height: UIScreen.main.bounds.height * 0.8)
} else {
// Static line
ZStack {
Line()
.stroke(Color.blue)
}
.frame(height: UIScreen.main.bounds.height * 0.8)
}
Spacer()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Side notes
In addiction to the problem above any good practice pointers on working with Swift are always welcome.
I made your project works, you can see the changed code // <<: Here!, the issue was there that you did not show the Animation the changed value! you showed just one time! and after that you keep it the same! if you see your code in your question you are repeating self.phase = .pi * 2 it makes no meaning to Animation! I just worked on your ContentView the all project needs refactor work, but that is not the issue here.
struct ContentView: View {
#State private var phase = 0.0
#State private var waveStrength: Double = 10.0
#State private var waveFrequency: Double = 10.0
#State var isAnimating: Bool = false
#State private var randNum: Int16 = 0
#State private var isNumberInteresting: Bool = false
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var stringOfText: String = String() // <<: Here!
func stopTimer() {
self.timer.upstream.connect().cancel()
phase = 0.0 // <<: Here!
}
func startTimer() {
self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(500)) { phase = .pi * 2 } // <<: Here!
}
func checkNumber(num: Int16) -> Bool {
var isInteresting: Bool = false
if num % 2 == 0 {
isInteresting.toggle()
}
return isInteresting
}
var body: some View {
VStack {
Button(isAnimating ? "Stop" : "Start") { // <<: Here!
isAnimating.toggle() // <<: Here!
isAnimating ? startTimer() : stopTimer() // <<: Here!
}
.font(.title)
.foregroundColor(isAnimating ? Color.red : Color.blue) // <<: Here!
ZStack {
if isAnimating {
ForEach(0..<10) { waveIteration in
Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
.stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
.offset(y: CGFloat(waveIteration) * 10)
}
}
else {
Line().stroke(Color.blue)
}
}
.frame(height: UIScreen.main.bounds.height * 0.8)
.overlay(isAnimating ? Text(stringOfText) : nil, alignment: .top) // <<: Here!
.onReceive(timer) { _ in
if isAnimating { // <<: Here!
randNum = Int16.random(in: 0..<Int16.max)
isNumberInteresting = checkNumber(num: randNum)
stringOfText = "Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))" // <<: Here!
if isNumberInteresting {
waveFrequency = 50.0
waveStrength = 50.0
} else {
waveFrequency = 10.0
waveStrength = 10.0
}
}
else {
stopTimer() // For safety! Killing Timer in case! // <<: Here!
}
}
.animation(nil, value: stringOfText) // <<: Here!
.animation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) // <<: Here!
.id(isAnimating) // <<: Here!
}
}
}
I adapted this code for SwiftUI to display confetti particles, but sometimes the particle emitter does not work. I've noticed that this often happens after sending to background (not killing the app entirely) and reopening it, or simply letting the app sit for a while then trying again.
I've tried using beginTime as other answers have mentioned (on both the emitter and cells), but that fully breaks things. I've also tried toggling various other emitter properties (birthRate, isHidden). It might have to do with the fact that I'm adapting this with UIViewRepresentable. It seems like the emitter layer just disappears, even though the debug console says its still visible.
class ConfettiParticleView: UIView {
var emitter: CAEmitterLayer!
public var colors: [UIColor]!
public var intensity: Float!
private var active: Bool!
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
colors = [UIColor(Color.red),
UIColor(Color.blue),
UIColor(Color.orange),
]
intensity = 0.7
active = false
emitter = CAEmitterLayer()
emitter.emitterPosition = CGPoint(x: UIScreen.main.bounds.width / 2.0, y: 0) // emit from top of view
emitter.emitterShape = .line
emitter.emitterSize = CGSize(width: UIScreen.main.bounds.width, height: 100) // line spans the whole top of view
// emitter.beginTime = CACurrentMediaTime()
var cells = [CAEmitterCell]()
for color in colors {
cells.append(confettiWithColor(color: color))
}
emitter.emitterCells = cells
emitter.allowsGroupOpacity = false
self.layer.addSublayer(emitter)
}
func startConfetti() {
emitter.lifetime = 1
// i've tried toggling other properties here like birthRate, speed
active = true
}
func stopConfetti() {
emitter.lifetime = 0
active = false
}
func confettiWithColor(color: UIColor) -> CAEmitterCell {
let confetti = CAEmitterCell()
confetti.birthRate = 32.0 * intensity
confetti.lifetime = 15.0 * intensity
confetti.lifetimeRange = 0
confetti.name = "confetti"
confetti.color = color.cgColor
confetti.velocity = CGFloat(450.0 * intensity) // orig 450
confetti.velocityRange = CGFloat(80.0 * intensity)
confetti.emissionLongitude = .pi
confetti.emissionRange = .pi / 4
confetti.spin = CGFloat(3.5 * intensity)
confetti.spinRange = 300 * (.pi / 180.0)
confetti.scaleRange = CGFloat(intensity)
confetti.scaleSpeed = CGFloat(-0.1 * intensity)
confetti.contents = #imageLiteral(resourceName: "confetti").cgImage
confetti.beginTime = CACurrentMediaTime()
return confetti
}
func isActive() -> Bool {
return self.active
}
}
view representable
struct ConfettiView: UIViewRepresentable {
#Binding var isStarted: Bool
func makeUIView(context: Context) -> ConfettiParticleView {
return ConfettiParticleView()
}
func updateUIView(_ uiView: ConfettiParticleView, context: Context) {
if isStarted && !uiView.isActive() {
uiView.startConfetti()
print("confetti started")
} else if !isStarted {
uiView.stopConfetti()
print("confetti stopped")
}
}
}
swiftui view for testing
struct ConfettiViewTest: View {
#State var isStarted = false
var body: some View {
ZStack {
ConfettiView(isStarted: $isStarted)
.ignoresSafeArea()
Button(action: {
isStarted = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
isStarted = false
}
}) {
Text("toggle")
.padding()
.background(Color.white)
}
}
}
}
I have an UIView which slides in from the Botton after you 1) tap or 2) slide it to the top. Right now, you can close it by 1) tap it again or 2) slide it to the bottom.
I want to prevent the: Close the view after a single tap and only allow to slide it down to close it.
This is my current code: (from: https://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator)
/// A pan gesture that enters into the `began` state on touch down instead of waiting for a touches moved event.
class InstantPanGestureRecognizer: UIPanGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if (self.state == UIGestureRecognizer.State.began) { return }
super.touchesBegan(touches, with: event)
self.state = UIGestureRecognizer.State.began
}
}
#objc private func popupViewPanned(recognizer: UIPanGestureRecognizer) {
print(recognizer.state.rawValue)
switch recognizer.state {
case .began:
// Start the animations
animateTransitionIfNeeded(to: currentState.opposite, duration: 0.5)
// Pause all animations, since the next event may be a pan changed
runningAnimators.forEach { $0.pauseAnimation() }
// Keep track of each animator's progress
animationProgress = runningAnimators.map { $0.fractionComplete }
case .changed:
// Variable setup
let translation = recognizer.translation(in: popupView)
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 1334, 1920, 2208:
var fraction = -translation.y / popupOffset
// adjust the fraction for the current state and reversed state
if currentState == .open { fraction *= -1 }
if runningAnimators[0].isReversed { fraction *= -1 }
// apply the new fraction
for (index, animator) in runningAnimators.enumerated() {
animator.fractionComplete = fraction + animationProgress[index]
}
case 2436, 2688, 1792:
var fraction = -translation.y / popupOffsetNotch
// adjust the fraction for the current state and reversed state
if currentState == .open { fraction *= -1 }
if runningAnimators[0].isReversed { fraction *= -1 }
// apply the new fraction
for (index, animator) in runningAnimators.enumerated() {
animator.fractionComplete = fraction + animationProgress[index]
}
default:
var fraction = -translation.y / popupOffsetNotch
// adjust the fraction for the current state and reversed state
if currentState == .open { fraction *= -1 }
if runningAnimators[0].isReversed { fraction *= -1 }
// apply the new fraction
for (index, animator) in runningAnimators.enumerated() {
animator.fractionComplete = fraction + animationProgress[index]
}
}
}
case .ended:
// variable setup
let yVelocity = recognizer.velocity(in: popupView).y
let shouldClose = yVelocity > 0
// if there is no motion, continue all animations and exit early
if yVelocity == 0 {
runningAnimators.forEach { $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) }
break
}
// reverse the animations based on their current state and pan motion
switch currentState {
case .open:
if !shouldClose && !runningAnimators[0].isReversed { runningAnimators.forEach { $0.isReversed = !$0.isReversed } }
if shouldClose && runningAnimators[0].isReversed { runningAnimators.forEach { $0.isReversed = !$0.isReversed } }
case .closed:
if shouldClose && !runningAnimators[0].isReversed { runningAnimators.forEach { $0.isReversed = !$0.isReversed } }
if !shouldClose && runningAnimators[0].isReversed { runningAnimators.forEach { $0.isReversed = !$0.isReversed } }
}
// continue all animations
runningAnimators.forEach { $0.continueAnimation(withTimingParameters: nil, durationFactor: 0) }
default:
()
}
}
/// Animates the transition, if the animation is not already running.
private func animateTransitionIfNeeded(to state: State, duration: TimeInterval) {
// ensure that the animators array is empty (which implies new animations need to be created)
guard runningAnimators.isEmpty else { return }
// an animator for the transition
let transitionAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: 1, animations: {
switch state {
case .open:
self.bottomConstraint.constant = 0
self.popupView.layer.cornerRadius = 10
case .closed:
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 1334, 1920, 2208:
self.bottomConstraint.constant = self.popupOffset
case 2436, 2688, 1792:
self.bottomConstraint.constant = self.popupOffsetNotch
default:
self.bottomConstraint.constant = self.popupOffsetNotch
}
}
self.popupView.layer.cornerRadius = 5
}
self.view.layoutIfNeeded()
})
// the transition completion block
transitionAnimator.addCompletion { position in
// update the state
switch position {
case .start:
self.currentState = state.opposite
case .end:
self.currentState = state
case .current:
()
#unknown default:
return
}
// manually reset the constraint positions
switch self.currentState {
case .open:
self.bottomConstraint.constant = 0
case .closed:
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 1334, 1920, 2208:
self.bottomConstraint.constant = self.popupOffset
case 2436, 2688, 1792:
self.bottomConstraint.constant = self.popupOffsetNotch
default:
self.bottomConstraint.constant = self.popupOffsetNotch
}
}
}
// remove all running animators
self.runningAnimators.removeAll()
}
// start all animators
transitionAnimator.startAnimation()
// keep track of all running animators
runningAnimators.append(transitionAnimator)
}
Solution: panRecognizer.delegate = self + UIGestureRecognizerDelegate +
// This function is needed to add the recognizer only to its parent view.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == gestureRecognizer.view
}
and use child views.
I am trying to make a program by using SwiftUI in which you can just drag a bunch of squares along. Here, there are two squares namely X and Y.
Everything works fine until you drag the squares along. After you drag and drop the first square the second square immediately follows it. I am not sure why this happens. I tried making every single constant and variable in these squares different but still after you drag and drop one of the squares the second just follows along.
My code is:
struct DemoSceneKit: View {
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
case dragging2(translation2: CGSize)
var translation: CGSize {
switch self {
case .inactive, .pressing, .dragging2:
return .zero
case .dragging(let translation):
return translation
}
}
var translation2: CGSize {
switch self {
case .inactive, .pressing, .dragging:
return .zero
case .dragging2(let translation2):
return translation2
}
}
var isActive: Bool {
switch self {
case .inactive:
return false
case .pressing, .dragging, .dragging2:
return true
}
}
var isDragging: Bool {
switch self {
case .inactive, .pressing:
return false
case .dragging, .dragging2:
return true
}
}
}
#GestureState var dragState2 = DragState.inactive
#GestureState var dragState = DragState.inactive
#State var viewState = CGSize.zero
var body: some View {
//First Square
let minimumLongPressDuration = 0.2
let longPressDrag = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture())
.updating($dragState) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag):
state = .dragging(translation: drag?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag?) = value else { return }
self.viewState.width += drag.translation.width
self.viewState.height += drag.translation.height
}
//Second square
let longPressDrag2 = LongPressGesture(minimumDuration: minimumLongPressDuration)
.sequenced(before: DragGesture())
.updating($dragState2) { value, state, transaction in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag2):
state = .dragging2(translation2: drag2?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag2?) = value else { return }
self.viewState.width += drag2.translation.width
self.viewState.height += drag2.translation.height
}
return
ZStack{
// First Square
Rectangle()
.stroke(Color.white, lineWidth: 5)
.frame(width: 100, height: 100)
.offset(
x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.gesture(longPressDrag)
Text("X")
.offset(x: viewState.width + dragState.translation.width,
y: viewState.height + dragState.translation.height
)
.gesture(longPressDrag)
.frame(width:200, height: 200)
// Second Square
ZStack{
Rectangle()
.stroke(Color.blue, lineWidth: 5)
.frame(width: 100, height: 100)
.position(x: 350, y: 300)
.offset(
x: viewState.width + dragState2.translation.width,
y: viewState.height + dragState2.translation.height
)
.gesture(longPressDrag2)
Text("Y")
.offset(x: viewState.width + dragState2.translation.width,
y: viewState.height + dragState2.translation.height
)
.gesture(longPressDrag2)
.frame(width:400, height: 300)
.position(x: 350, y: 300)
}
}
}
}
Please help me with this code.