DEV Community

Hide ⛄
Hide ⛄

Posted on

How to create a View that reveals hidden buttons when slid in SwiftUI

Introduction

This article introduces how to implement a View in SwiftUI, in which a hidden button appears from the right when the view is slid to the left.

The image of a view that reveals hidden buttons when slid

Implementation

This can be accomplished by changing the offset of the x-axis of the View as the user drags the view.

The code executable in Xcode's Playground is as follows

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        VStack(spacing: 0) {
            SlideButtons()
                .frame(width: 300, height: 40)
            SlideButtons()
                .frame(width: 300, height: 40)
            SlideButtons()
                .frame(width: 300, height: 40)
        }
    }
}

struct SlideButtons: View {
    // The current x offset of the view.
    @State private var xOffset = CGFloat.zero

    // The default x offset when a drag ends.
    @State private var defaultXOffset = CGFloat.zero

    // Initialize it on `.onAppear()`.
    @State private var hiddenButtonWidth: CGFloat = 0

    @State private var hiddenButtonMaxWidth: CGFloat = 50

    // Ratio of a hidden button width to the entire View.
    private let buttonSizeRatio = 6

    private var dragGesture: some Gesture {
        DragGesture(minimumDistance: 30, coordinateSpace: .local)
            .onChanged { value in
                self.xOffset += value.translation.width
                if self.hiddenButtonMaxWidth * -2 <= self.xOffset && self.xOffset <= 0 {
                    self.hiddenButtonWidth = min(self.xOffset / -2, self.hiddenButtonMaxWidth)
                }
            }
            .onEnded { value in
                withAnimation() {
                    if self.areHiddenButtonsShowed() {
                        if (self.xOffset - self.defaultXOffset) > self.getMinDistanceToChangeDefaultXOffset() {
                            self.hideButtons()
                        } else {
                            // Return to the current default position.
                            self.xOffset = self.defaultXOffset
                            // Reset the button size
                            self.hiddenButtonWidth = self.hiddenButtonMaxWidth
                        }

                    } else {
                        if (self.defaultXOffset - self.xOffset) > self.getMinDistanceToChangeDefaultXOffset() {
                            self.showHiddenButtons()
                        } else {
                            // Return to the current default position.
                            self.xOffset = self.defaultXOffset
                            // Reset the button size
                            self.hiddenButtonWidth = 0
                        }
                    }
                }
            }
    }

    var body: some View {
        GeometryReader { geometry in
            HStack(spacing: 0) {
                // Main button
                Rectangle()
                    .foregroundColor(.black)
                    .overlay {
                        HStack {
                            Text("Read a book")
                                .foregroundColor(.white)
                                .padding()
                            Spacer()
                        }
                    }
                    .frame(width: geometry.size.width,
                           height: geometry.size.height)
                    .gesture(
                        self.dragGesture
                    )
                    .onTapGesture {
                        withAnimation() {
                            if self.areHiddenButtonsShowed() {
                                self.hideButtons()
                            } else {
                                self.showHiddenButtons()
                            }
                        }
                    }

                // 1st hidden button
                Rectangle()
                    .frame(width: self.hiddenButtonWidth, height: geometry.size.height)
                    .foregroundColor(Color.yellow)
                    .overlay {
                        Text("Skip")
                            .foregroundColor(.white)
                    }

                // 2nd hidden button
                Rectangle()
                    .frame(width: self.hiddenButtonWidth, height: geometry.size.height)
                    .foregroundColor(Color.red)
                    .overlay {
                        Text("Done")
                            .foregroundColor(.white)
                    }
            }
            .onAppear() {
                self.hiddenButtonWidth = 0
                self.hiddenButtonMaxWidth = geometry.size.width / CGFloat(self.buttonSizeRatio)
                self.hideButtons()
            }
            .frame(width: geometry.size.width, height: geometry.size.height, alignment: .leading)
            .offset(x: self.xOffset)
        }
    }

    private func areHiddenButtonsShowed() -> Bool {
        return self.defaultXOffset == self.getXOffsetToShowHiddenButtons()
    }

    private func hideButtons() {
        self.hiddenButtonWidth = 0
        self.xOffset = CGFloat.zero
        self.defaultXOffset = .zero
    }

    private func getMinDistanceToChangeDefaultXOffset() -> CGFloat {
        return self.hiddenButtonMaxWidth / 2
    }

    private func getXOffsetToShowHiddenButtons() -> CGFloat {
        return (self.hiddenButtonMaxWidth * 2) * -1
    }

    private func showHiddenButtons() {
        self.hiddenButtonWidth = self.hiddenButtonMaxWidth
        self.xOffset = self.getXOffsetToShowHiddenButtons()
        self.defaultXOffset = self.getXOffsetToShowHiddenButtons()
    }
}

PlaygroundPage.current.liveView = UIHostingController(rootView: ContentView())

Enter fullscreen mode Exit fullscreen mode

Reference

Top comments (0)