Search code examples
iosswiftuidrag

Incorrect height calculation with DragGesture() in SwiftUI


I'm doing a Map based app with SwiftUI and I need to change the height of modal Card (this is a view where the map info will be located) via dragging it up/down, like in Apple Maps.

Here is the screenshot of Card which height Im trying to interact with https://i.sstatic.net/mZX2m.png

I've implemented a ZStack with RoundedRectangle and added a DragGesture() modifier.

            ZStack(alignment: .top){
                RoundedRectangle(cornerRadius: 16.0)
                    .frame(height:currentHeight)
                Text("Card")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(Color.white)
                    .multilineTextAlignment(.center)
                    .padding(.top)
            }.gesture(DragGesture()
                .onChanged { value in
                    if (self.currentHeight + value.predictedEndLocation.y - value.startLocation.y > UIScreen.main.bounds.height) {
                        self.currentHeight = UIScreen.main.bounds.height
                    } else {
                        self.currentHeight += value.predictedEndLocation.y - value.startLocation.y
                    }
                }
                .onEnded { value in
                    if (self.currentHeight + value.predictedEndLocation.y - value.startLocation.y > UIScreen.main.bounds.height) {
                        self.currentHeight = UIScreen.main.bounds.height
                    } else {
                        self.currentHeight += value.predictedEndLocation.y - value.startLocation.y
                    }
                })

The first problem was with dragging up, the card went over screen height and Im checking for UIScreen bounds height to prevent height being bigger than screen, but there are more problems with calculations, Card's height gets ambiguous very fast and it doesn't want to drag down.

I have never worked with gesture recognizers. Can you tell me the correct way of calculations?


Solution

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