Skip to content

Instantly share code, notes, and snippets.

@kildos
Created August 14, 2024 16:49
Show Gist options
  • Save kildos/d284bad0b9c9e7f81c5ca6dd19bf4f25 to your computer and use it in GitHub Desktop.
Save kildos/d284bad0b9c9e7f81c5ca6dd19bf4f25 to your computer and use it in GitHub Desktop.
SwiftUI customizable bottom sheet with optional button
import SwiftUI
struct CustomBottomSheetView: View {
@State var title: LocalizedStringKey
@State var subtitle: LocalizedStringKey
@State var actionText: LocalizedStringKey
@State var showingSheet = false
var action: (() -> Void)? = nil
var dismissed: () -> Void
var body: some View {
VStack {
Spacer()
if showingSheet {
VStack {
Text(title)
.font(.title3)
.padding(.vertical, 20)
.frame(maxWidth: .infinity)
Text(subtitle)
.font(.body)
if let action = action {
RoundedButton(title: actionText) {
withAnimation {
showingSheet = false
}
action()
}
.padding(.top, 20)
}
}
.transition(.move(edge: .bottom))
.multilineTextAlignment(.center)
.foregroundStyle(.black)
.padding(.vertical, 20)
.padding(.horizontal, 10)
.background(
RoundedRectangle(cornerRadius: 15)
.foregroundStyle(.white)
)
.overlay(alignment: .topTrailing, content: {
Button(action: {
withAnimation(.snappy) {
showingSheet = false
dismissed()
}
}, label: {
ZStack {
Circle()
.fill(Color.gray)
.frame(height: 20)
Image(systemName: "xmark")
.font(.system(size: 15, weight: .bold, design: .rounded))
.foregroundColor(.white)
}
.padding(10)
.contentShape(Circle())
})
})
.padding()
}
}
.frame(maxWidth: .infinity)
.background(
.black
.opacity(0.5)
)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
withAnimation (.snappy(duration: 0.4, extraBounce: 0.2)) {
self.showingSheet = true
}
})
}
}
}
struct RoundedButton: View {
@State var title: LocalizedStringKey
var color: Color = .tabBarAccent
var titleColor: Color = .whiteAndBlack
var action: () -> Void
var body: some View {
Button {
action()
} label: {
Text(title)
.font(.title3)
.foregroundStyle(titleColor)
.frame(maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: 44)
}
.tint(color)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.roundedRectangle)
}
}
//USAGE EXAMPLE
struct ShowcaseBottomSheet: View {
@State var showingBottomSheetWithButton = false
@State var showingBottomSheet = false
var body: some View {
VStack (spacing: 50) {
Button(action: {
withAnimation {
showingBottomSheet.toggle()
}
}, label: {
Text("⬇️ Show bottom sheet⬇️")
})
.foregroundStyle(.blue)
Button(action: {
withAnimation {
showingBottomSheetWithButton.toggle()
}
}, label: {
Text("⬇️ Show bottom sheet with button⬇️")
})
.foregroundStyle(.blue)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay {
if showingBottomSheetWithButton {
CustomBottomSheetView(title: "New workout detected",
subtitle: "Do you want to add details to the workout now?",
actionText: "Add details",
action: {
print("Button tapped!")
withAnimation {
showingBottomSheetWithButton.toggle()
}
},
dismissed: {
showingBottomSheetWithButton.toggle()
})
}
if showingBottomSheet {
CustomBottomSheetView(title: "New workout detected",
subtitle: "Do you want to add details to the workout now?",
actionText: "Add details",
dismissed: {
showingBottomSheet.toggle()
})
}
}
}
}
#Preview {
ShowcaseBottomSheet()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment