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
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.
@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
Swiftui: Update Navigationview After Deletion (Ipad)
Subclass Uiscrollview in Swift for Touches Began & Touches Moved
How to Draw Two Polylines in Different Colors in Mapkit
Scntext Alignment Not Working in iOS
How to Prevent a Spacer to Make a VStack Greedly Grow Beyond Necessary
Drawing Pixels on the Screen Using Coregraphics in Swift
How to Trim a String in Swift Based on a Character
Getting an Issue with Upgrade to Xcode 10.2
Nsposixerrordomain When Binding to Socket on MACos 10.12
Module Compiled with Swift 4.0 Cannot Be Imported in Swift 3.0.2
Using Xcode to Cross-Compile Swift to Linux
Increment Integer in Nsuserdefaults
Swift Codable - Parse JSON Array Which Can Contain Different Data Type
Getting a Segmentation Fault: 11 with Swift 5.2 When Using Filemanager.Default.Currentdirectorypath
How to Prevent Eventstore Access Error on First Run
Syncconfiguration Deprecated, What Is the Proper Use of Syncuser.Configuration()