Limit Rectangle to Screen Edge on Drag Gesture

Limit rectangle to screen edge on drag gesture

Here is a demo of possible approach (for any view, cause view frame is read dynamically).

Demo & tested with Xcode 12 / iOS 14

Sample Image

struct ViewSizeKey: PreferenceKey {
static var defaultValue = CGSize.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}

struct ContentView: View {
@State private var pogPosition = CGPoint()
@State private var size = CGSize.zero

var body: some View {
GeometryReader { gp in
PogSound()
.background(GeometryReader {
Color.clear
.preference(key: ViewSizeKey.self, value: $0.frame(in: .local).size)
})
.onPreferenceChange(ViewSizeKey.self) {
self.size = $0
}
.position(pogPosition)
.gesture(
DragGesture()
.onChanged { value in
let rect = gp.frame(in: .local)
.inset(by: UIEdgeInsets(top: size.height / 2.0, left: size.width / 2.0, bottom: size.height / 2.0, right: size.width / 2.0))
if rect.contains(value.location) {
self.pogPosition = value.location
}
}
.onEnded { value in
print(value.location)
}
)
.onAppear {
let rect = gp.frame(in: .local)
self.pogPosition = CGPoint(x: rect.midX, y: rect.midY)
}
}.edgesIgnoringSafeArea(.all)
}
}

Limit drag to rectangle bounds in SwiftUI

You're almost there. As you're outer shape is a rect, you don't need to calculate complicated distances. It's enough to check whether the position is still in the outer rect, and limit drag to its size values (0...rectSize). If you don't want your selection frame to move over the borders, you have to correct by 1/2 of its size (rectSize / 4 / 2)

struct ContentView: View {

@State private var position = CGPoint(x: 100, y: 100)
private var rectSize: CGFloat = 350

var body: some View {
VStack {
Text("Current postion = (x: \(Int(position.x)), y: \(Int(position.y))")

Rectangle()
.fill(.gray)
.frame(width: rectSize, height: rectSize)
.overlay(
Rectangle()
.fill(.clear)
.border(.blue, width: 2.0)
.contentShape(Rectangle())
.frame(width: rectSize / 4, height: rectSize / 4)
.position(position)
.gesture(
DragGesture()
.onChanged { value in
// limit movement to min and max value
let limitedX = max(min(value.location.x, rectSize - rectSize / 8), rectSize / 8)
let limitedY = max(min(value.location.y, rectSize - rectSize / 8), rectSize / 8)

self.position = CGPoint(x: limitedX,
y: limitedY)
}
)
)
}
}
}

SwiftUI - Restricting drag of a rectangle inside a view

check this out:

struct ContentView: View {
@State private var currentPosition: CGSize = .zero
@State private var newPosition: CGSize = .zero
@State private var parentRect: CGRect = .zero
@State private var childRect: CGRect = .zero

func correctPostion() {
print(self.currentPosition)
if self.currentPosition.width > 100 {
self.currentPosition.width = 100
}
if self.currentPosition.height > 200 {
self.currentPosition.height = 200
}
if self.currentPosition.width < -100 {
self.currentPosition.width = -100
}
if self.currentPosition.height < -200 {
self.currentPosition.height = -200
}
}

var body: some View {
VStack {
Rectangle().frame(width: 100, height: 100, alignment: .top)
.foregroundColor(.blue)
.offset(x: self.currentPosition.width, y: self.currentPosition.height)
.background(GeometryGetter(rect: $childRect))

.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { value in
self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)

self.correctPostion()
}
.onEnded { value in
self.currentPosition = CGSize(width: value.translation.width + self.newPosition.width, height: value.translation.height + self.newPosition.height)

self.correctPostion()

self.newPosition = self.currentPosition
}
)
}
.frame(width: 300, height: 500, alignment: .center)
.border(Color.black, width: 1)
.background(GeometryGetter(rect: $parentRect))
}
}

struct GeometryGetter: View {
@Binding var rect: CGRect

var body: some View {
GeometryReader { geometry in
Group { () -> AnyView in
DispatchQueue.main.async {
self.rect = geometry.frame(in: .global)
}
return AnyView(Color.clear)
}
}
}
}

How to set size limit using DragGesture() when resizing a shape in SwiftUI?

You can use GeometryReader to get subview size and restrict size with min condition.

struct CustomDraggableComponent: View {
@State var height: CGFloat = 200
@State var width: CGFloat = 200

@State private var maxWidth = 0
@State private var maxHeight = 0

var body: some View {
GeometryReader { geo in
VStack(alignment: .center) {
Rectangle()
.fill(Color.red)
.frame(minWidth: width, maxWidth: width, minHeight: height, maxHeight: height)

HStack {
Spacer()
Rectangle()
.fill(Color.gray)
.frame(width: 80, height: 30)
.cornerRadius(10)
.overlay(Text("Drag"))
.gesture(
DragGesture()
.onChanged { value in
// This code allows resizing view min 200 and max to parent view size
height = min(max(200, height + value.translation.height), geo.size.height - 45) // 45 for Drag button height + padding
width = min(max(200, height + value.translation.width), geo.size.width)
}
)
Spacer()
}
} .frame(width: geo.size.width, height: geo.size.height)
}
}
}

Rotation of view goes backwards with drag gesture

Something I wrote for UIKit that may be adapted to SwiftUI :

// rotation from point lPrevPoint to lTouchPoint
let lDeltaDirectionX = Double((lTouchPoint.x - lPrevPoint.x) / 2.0)
let lDeltaDirectionY = Double((lTouchPoint.y - lPrevPoint.y) / 2.0)
// only do 1 direction at a time ( the one with biggest change)
// then you check in which part of the screen you are :
if abs(lDeltaDirectionX) > abs(lDeltaDirectionY) {
if lPrevPoint.y > self.frame.size.height / 2.0 {
orientation = fmod(orientation-lDeltaDirectionX + 360.0, 360.0)
} else {
orientation = fmod(orientation+lDeltaDirectionX + 360.0, 360.0)
}
}
else
{
if lPrevPoint.x > self.frame.size.width / 2.0 {
orientation = fmod(orientation+lDeltaDirectionY + 360.0, 360.0)
} else {
orientation = fmod(orientation-lDeltaDirectionY + 360.0, 360.0)
}
}

orientation is the angle of rotation . Not perfect because it does not take into account the distance from view center to have a more accurate value. But can be a good start.

Adding Drag Gesture to image inside a ForEach in SwiftUI

Here is possible approach - to keep selection during drag. Tested with Xcode 11.4 / iOS 13.4.

demo

@GestureState private var dragOffset = CGSize.zero
@State private var selected: Plant? = nil // << your plant type here
var body: some View {

//Plants Headline
VStack (spacing: 5) {
Text("Plants")
.font(.headline)
.fontWeight(.light)

//HStack for Plants Images
HStack (spacing: 10) {

//For each that loops through all of the plants inside the plant box
ForEach(plantBoxViewModel.plants) { plant in

//Image of the individual plant (obtained via http request to the backend)
Image(base64String: plant.picture)
.resizable()
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.offset(x: self.selected == plant ? self.dragOffset.width : 0,
y: self.selected == plant ? self.dragOffset.height : 0)
.animation(.easeInOut)
.aspectRatio(contentMode: .fill)
.frame(width: 70, height: 70)
.gesture(
DragGesture()
.updating(self.$dragOffset, body: { (value, state, transaction) in
if nil == self.selected {
self.selected = plant
}
state = value.translation
}).onEnded { _ in self.selected = nil }
)
}
}
}

}


Related Topics



Leave a reply



Submit