Created
August 14, 2024 16:49
-
-
Save kildos/d284bad0b9c9e7f81c5ca6dd19bf4f25 to your computer and use it in GitHub Desktop.
SwiftUI customizable bottom sheet with optional button
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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