Reputation: 2157
I'm trying to add to my app bottom sheet with responsive height which I can set programmatically. For this purpose I'm trying to use this video. Here is code of my view controller:
struct SecondView: View {
@State var cardShown = false
@State var cardDismissal = false
var body: some View {
Button {
cardShown.toggle()
cardDismissal.toggle()
} label: {
Text("Show card")
.bold()
.foregroundColor(Color.white)
.background(Color.red)
.frame(width: 200, height: 50)
}
BottomCard(cardShown: $cardShown, cardDismissal: $cardDismissal) {
CardContent()
}
}
}
struct CardContent:View{
var body: some View{
Text("some text")
}
}
struct BottomCard<Content:View>:View{
@Binding var cardShown:Bool
@Binding var cardDismissal:Bool
let content:Content
init(cardShown:Binding<Bool> , cardDismissal:Binding<Bool>, @ViewBuilder content: () -> Content){
_cardShown = cardShown
_cardDismissal = cardDismissal
self.content = content()
}
var body: some View{
ZStack{
//Dimmed
GeometryReader{ _ in
EmptyView()
}
.background(Color.red.opacity(0.2))
.opacity(cardShown ? 1 : 0)
.animation(.easeIn)
.onTapGesture {
cardShown.toggle()
}
// Card
VStack{
Spacer()
VStack{
content
}
}
.edgesIgnoringSafeArea(.all)
}
}
}
but after pressing the button I don't see any pushed bottom menu. I checked and it seems that I have similar code to this video but on the video bottom sheet appears. Maybe I missed something important for menu showing. The main purpose is to show bottom menu with responsive height which will wrap elements and will be able to change menu height. I tried to use .sheet()
but this element has stable height as I see. I know that from the ios 15+ we will have some solutions for this problem but I would like to create something more stable and convenient :)
Upvotes: 1
Views: 2791
Reputation: 101
The accepted answer works only if your minimum iOS version is 16. To support lower iOS versions, here's a custom bottom sheet implementation with height customization:
BottomSheetView
struct BottomSheetView<Content: View>: View {
let content: Content
let maxHeight: CGFloat
let minHeight: CGFloat
@Binding var isPresented: Bool
@GestureState private var translation: CGFloat = 0
private var offset: CGFloat {
isPresented ? 0 : maxHeight
}
private var indicator: some View {
RoundedRectangle(cornerRadius: 5)
.fill(Color.gray.opacity(0.5))
.frame(
width: 40,
height: 6
)
.padding(8)
}
init(isPresented: Binding<Bool>, maxHeight: CGFloat, @ViewBuilder content: () -> Content) {
self.content = content()
self.maxHeight = maxHeight
self.minHeight = maxHeight * 0.3
self._isPresented = isPresented
}
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator
self.content
}
.frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
.background(Color(.systemBackground))
.cornerRadius(20)
.frame(height: geometry.size.height, alignment: .bottom)
.offset(y: self.offset + self.translation)
.animation(.interactiveSpring())
.gesture(
DragGesture().updating(self.$translation) { value, state, _ in
state = value.translation.height
}.onEnded { value in
let snapDistance = self.maxHeight * 0.3
guard abs(value.translation.height) > snapDistance else {
return
}
self.isPresented = value.translation.height < 0
}
)
}
.edgesIgnoringSafeArea(.all)
}
}
How to Use it
add this bottom sheet to your base ZStack
view by binding its visibility to a boolean variable
BottomSheetView(isPresented: $viewModel.isBottomSheetVisible, maxHeight: 400) {
VStack(spacing: 20) {
Text("Terms and Conditions!")
.font(.title)
.fontWeight(.bold)
}
.padding()
}
Upvotes: 1
Reputation: 119272
We can have native SwiftUI resizable sheet (like UIKit). This is possible with the new .presentationDetents()
modifier.
.sheet(isPresented: $showBudget) {
BudgetView()
.presentationDetents([.height(250), .medium])
.presentationDragIndicator(.visible)
}
Upvotes: 2
Reputation: 14935
iPadOS 16.0+
macOS 13.0+
Mac Catalyst 16.0+
tvOS 16.0+
watchOS 9.0+
Use presentationDetents(_:)
struct ContentView: View {
@State private var isBottomSheetVisible = false
var body: some View {
Button("View Settings") {
isBottomSheetVisible = true
}
.sheet(isPresented: $isBottomSheetVisible) {
Text("Bottom Sheet")
.presentationDetents([.height(250), .medium])
.presentationDragIndicator(.visible)
}
}
}
Upvotes: 1
Reputation: 21
what you want to do is to have a card that only exists when there is a certain standard met. If you want to push up a card from the bottom then you can make a view of a card and put it at the bottom of a Zstack view using a geometry reader and then make a button that only allows for that card to exist when the button is pressed INSTEAD of trying to hire it by changing its opacity. Also, make sure you move the dismissal button to the inside of the cad you have.
Heres an example you can try :
struct SecondView: View {
@State var cardShown = false
var body: some View {
GeometryReader{
ZStack {
ZStack{
// I would also suggest getting used to physically making your
//button and then giving them functionality using a "Gesture"
Text("Show Button")
.background(Rectangle())
.onTapGesture{
let animation = Animation.spring()
withAnimation(animation){
self.cardShown.toggle
}
}
}
ZStack {
if cardShown == true{
BottomCard(cardShown: $cardShown) {
CardContent()
}
}
// here you can change how far up the card comes after the button
//is pushed by changing the "0"
.offset(cardShown == false ? geometry.size.height : 0)
}
}
}
}
}
Also, you don't need to have a variable for the card being shown and a variable for the card being dismissed. Just have one "cardShown" variable and make it so that when it is TRUE the card is shown and when it is FALSE (after hitting the button on the card or hitting the initial button again.) the card goes away.
Upvotes: 1
Reputation: 5084
struct BottomCard<Content:View>:View{
@Binding var cardShown:Bool
@Binding var cardDismissal:Bool
let content:Content
init(cardShown:Binding<Bool> , cardDismissal:Binding<Bool>, @ViewBuilder content: () -> Content){
_cardShown = cardShown
_cardDismissal = cardDismissal
self.content = content()
}
var body: some View{
ZStack{
//Dimmed
GeometryReader{ _ in
EmptyView()
}
.background(Color.red.opacity(0.2))
.animation(.easeIn)
.onTapGesture {
cardShown.toggle()
}
// Card
VStack{
Spacer()
VStack{
content
}
Spacer()
}
}.edgesIgnoringSafeArea(.all)
.opacity(cardShown ? 1 : 0)
}
}
So you just need to set the height!
Upvotes: 1