Incorrect Height Calculation with Draggesture() in Swiftui

Incorrect height calculation with DragGesture() in SwiftUI

Here is an approach (some helpful comments in code below)

SwiftUI view height resize

struct TestResizingCard: View {

static let kMinHeight: CGFloat = 100.0
@State var currentHeight: CGFloat = kMinHeight // << any initial

var body: some View {
GeometryReader { g in // << for top container height limit
ZStack(alignment: .bottom) {
Rectangle().fill(Color.yellow) // << just for demo

self.card()
.gesture(DragGesture()
.onChanged { value in
// as card is at bottom the offset is reversed
let newHeight = self.currentHeight - (value.location.y - value.startLocation.y)
if newHeight > Self.kMinHeight && newHeight < g.size.height {
self.currentHeight = newHeight
}
})
}
}
}

func card() -> some View {
ZStack(alignment: .top){
RoundedRectangle(cornerRadius: 16.0)
.frame(height:currentHeight)
Text("Card")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.padding(.top)
}
}
}

struct TestResizingCard_Previews: PreviewProvider {
static var previews: some View {
TestResizingCard()
}
}

How to change the height of the object by using DragGesture in SwiftUI?

The correct calculation is

.gesture(
DragGesture()
.onChanged { value in
height = max(MIN_HEIGHT, height + value.translation.height)
}
)

Also, remove the infinite width. It's invalid.

.frame(minHeight: 0, maxHeight: height)

or

.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: height)

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)
}
}

How to drag view for expand and collaps in SwiftUI?

Here is something I did once. Today you might want to do it with PreferenceKey.

struct ContentView: View {

let minHeight: CGFloat = 50
let startHeight: CGFloat = 50

@State private var currentHeight: CGFloat = 1000
@State private var contentHeight: CGFloat = 0


var textContent: some View {
VStack(alignment: .leading) {
Text("Text 1: orem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient.")
Text("Text 2: montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. ")
Text("Text 3: orem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur. ")
Text("Text 4: orem ipsum dolor sit amet, consectetuer adipiscing elit. Aene.")
Text("Text 5: orem ipsum dosque eu, pretium quis, sem. Nulla consequat massa quis enim. ")
Text("Text 6: orem ipsum dolor sit amet, consectetuer adipiscing elit. Aene.")
}
.padding()
}


var body: some View {
VStack {
VStack(alignment: .leading, spacing: 0) {
VStack(spacing: 0) {
Color.clear
.ignoresSafeArea()
.overlay(
textContent
.fixedSize(horizontal: false, vertical: true)
.overlay( GeometryReader { geo in Color.clear.onAppear {
contentHeight = geo.size.height
currentHeight = startHeight
}})

, alignment: .topLeading )
.clipped()

Spacer(minLength: 0)
}
.frame(height: currentHeight)

Button {
currentHeight = max(contentHeight, minHeight)
} label: {
Image(systemName: "rectangle.arrowtriangle.2.outward")
.padding(.horizontal)
}
.frame(maxWidth: .infinity, alignment: .leading)
.overlay(
Image(systemName: "line.3.horizontal").foregroundColor(.secondary)
.contentShape(Rectangle())

.gesture(DragGesture()
.onChanged { value in
currentHeight += value.translation.height
currentHeight = max(currentHeight, minHeight)
}
.onEnded { value in
currentHeight = max(currentHeight, minHeight)
}
)
)
.padding(5)
}
.background(.gray)
.padding(.horizontal)

Spacer()
}
}
}

Sample Image

SwiftUI DragGesture only in one direction

Yes, it is, by applying one of the two components (either horizontal or vertical) of the gesture translation to the view offset.

Here's such behaviour implemented as a ViewModifier.

struct DraggableModifier : ViewModifier {

enum Direction {
case vertical
case horizontal
}

let direction: Direction

@State private var draggedOffset: CGSize = .zero

func body(content: Content) -> some View {
content
.offset(
CGSize(width: direction == .vertical ? 0 : draggedOffset.width,
height: direction == .horizontal ? 0 : draggedOffset.height)
)
.gesture(
DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = .zero
}
)
}

}

Demo:

struct ContentView: View {

var body: some View {
VStack {
Spacer(minLength: 100)
HStack {
Rectangle()
.foregroundColor(.green)
.frame(width: 100, height: 100)
.modifier(DraggableModifier(direction: .vertical))
Text("Vertical")
}
Spacer(minLength: 100)
Rectangle()
.foregroundColor(.red)
.frame(width: 100, height: 100)
.modifier(DraggableModifier(direction: .horizontal))
Text(verbatim: "Horizontal")
Spacer(minLength: 100)
}
}
}

Result:

Sample Image



Related Topics



Leave a reply



Submit