Search code examples
swiftswiftuidraggesture

Increase/Decrease the size of a view horizontally by dragging the edges of it


I've seen a few similar examples of this such as How to correctly do up an adjustable split view in SwiftUI? and How to resize UIView by dragging from its edges? but I can't find exactly what I'm looking for that correlates correctly across to SwiftUI

I have a view that I want the user to be able to adjust the width of via a 'grab bar' on the right of the view. When the user drags this bar left (decreases view width) and to the right (increases the view width). How can I go about doing this?

In the example, RedRectangle is my view that i'm trying to adjust which comprises of a Rectangle and the Resizer which is manipulated to adjust the size. What am I doing wrong here?

Additionally, there isn't a gradual animation/transition of the frame being increased/decreased and it just seems to jump. How can I achieve this?

Reproducible example linked here:

import SwiftUI

struct ContentView: View {
    @State var resizedWidth: CGFloat?

    var body: some View {
        HStack(alignment: .center) {
            Spacer()
            RedRectangle(width: 175, resizedWidth: resizedWidth)
            Resizer()
                .gesture(
                    DragGesture()
                        .onChanged({ value in
                            resizedWidth = max(80, resizedWidth ?? 0 + value.translation.width)
                        })
                )
            Spacer()
        }
    }
}

struct RedRectangle: View {
    let width: CGFloat
    var resizedWidth: CGFloat?

    var body: some View {
        Rectangle()
            .fill(Color.red)
            .frame(width: resizedWidth != nil ? resizedWidth : width, height: 300)
            .frame(minWidth: 80, maxWidth: 400)
    }
}

struct Resizer: View {
    var body: some View {
        Rectangle()
            .fill(Color.blue)
            .frame(width: 8, height: 75)
            .cornerRadius(10)
    }
}

Solution

  • import SwiftUI
    
    struct ContentView: View {
        let minWidth: CGFloat = 100
        @State var width: CGFloat?
    
        var body: some View {
            HStack(alignment: .center) {
                Spacer()
                RedRectangle(width: width ?? minWidth)
                Resizer()
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                width = max(minWidth, width! + value.translation.width)
                            }
                    )
                Spacer()
            }
            .onAppear {
                width = minWidth
            }
        }
    }
    
    struct RedRectangle: View {
        let width: CGFloat
    
        var body: some View {
            Rectangle()
                .fill(Color.red)
                .frame(width: width, height: 100)
        }
    }
    
    struct Resizer: View {
        var body: some View {
            Rectangle()
                .fill(Color.blue)
                .frame(width: 8, height: 75)
                .cornerRadius(10)
        }
    }