Multiple Bottom sheets - the content doesn't load SwiftUI
The .sheet modifier will create the sheet view as soon as LogInView() is initialized. In your 'if.. else if..' statement, there is no logic to catch 'else' situations (situations where action == nil). Therefore, since action == nil on init(), the first .sheet that will present will fail your 'if..else if' and an EmptyView will present.
But don't worry! This is a common issue and can be easily solved. Here are 2 easy ways to implement methods to fix this (I prefer the 2nd method bc it's cleaner):
METHOD 1: Present a single view & change that view's content instead of switching between which view to present.
Instead of doing the 'if.. else if..' statement within the .sheet modifier, present a static view (I've called it SecondaryView ) that has a @Binding variable connected to your action. This way, when LogInView() appears, we can ensure that it will definitely render this view and then we can simply modify this view's content by changing the @Binding action.
import SwiftUI
struct LogInView: View {
enum Action{
case resetPW, signUp
}
@State private var showSheet = false
@State private var action: Action?
var body: some View {
LoginEmailView(showSheet: $showSheet, action: $action)
.sheet(isPresented: $showSheet) {
SecondaryView(action: $action)
}
}
}
struct LoginEmailView: View {
@Binding var showSheet: Bool
@Binding var action: LogInView.Action?
var body: some View {
VStack(spacing: 40 ){
Text("Forgot Password")
.onTapGesture {
action = .resetPW
showSheet.toggle()
}
Text("Sign Up")
.onTapGesture {
action = .signUp
showSheet.toggle()
}
}
}
}
struct SecondaryView: View {
@Binding var action: LogInView.Action?
var body: some View {
if action == .signUp {
Text("SIGN UP VIEW HERE")
} else {
Text("FORGOT PASSWORD VIEW HERE")
}
}
}
METHOD 2: Make each Button it's own View, so that it can have it's own .sheet modifier.
In SwiftUI, we are limited to 1 .sheet() modifier per View. However, we can always add Views within Views and each subview is then allowed it's own .sheet() modifier as well. So the easy solution is to make each of your buttons their own view. I prefer this method because we no longer need to pass around the @State/@Binding variables between views.
struct LogInView: View {
var body: some View {
LoginEmailView()
}
}
struct LoginEmailView: View {
var body: some View {
VStack(spacing: 40 ){
ForgotPasswordButton()
SignUpButton()
}
}
}
struct ForgotPasswordButton: View {
@State var showSheet: Bool = false
var body: some View {
Text("Forgot Password")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("FORGOT PASSWORD VIEW HERE")
})
}
}
struct SignUpButton: View {
@State var showSheet: Bool = false
var body: some View {
Text("Sign Up")
.onTapGesture {
showSheet.toggle()
}
.sheet(isPresented: $showSheet, content: {
Text("SIGN UP VIEW HERE")
})
}
}
Multiple sheet(isPresented:) doesn't work in SwiftUI
UPD
Starting from Xcode 12.5.0 Beta 3 (3 March 2021) this question makes no sense anymore as it is possible now to have multiple .sheet(isPresented:)
or .fullScreenCover(isPresented:)
in a row and the code presented in the question will work just fine.
Nevertheless I find this answer still valid as it organizes the sheets very well and makes the code clean and much more readable - you have one source of truth instead of a couple of independent booleans
The actual answer
Best way to do it, which also works for iOS 14:
enum ActiveSheet: Identifiable {
case first, second
var id: Int {
hashValue
}
}
struct YourView: View {
@State var activeSheet: ActiveSheet?
var body: some View {
VStack {
Button {
activeSheet = .first
} label: {
Text("Activate first sheet")
}
Button {
activeSheet = .second
} label: {
Text("Activate second sheet")
}
}
.sheet(item: $activeSheet) { item in
switch item {
case .first:
FirstView()
case .second:
SecondView()
}
}
}
}
Read more here: https://developer.apple.com/documentation/swiftui/view/sheet(item:ondismiss:content:)
To hide the sheet just set activeSheet = nil
Bonus:
If you want your sheet to be fullscreen, then use the very same code, but instead of .sheet
write .fullScreenCover
SwiftUI Issues with sheet modifier
.sheet
being embedded inside a List
does only open once is a known bug and I hope they fix it. Until then you have to move .sheet
outside of any List
.
But since the .sheet
is not inside a List
but inside a NavigationView
, it might a good idea to try to move the .sheet
outside of it.
But do not attach two .sheet
two the same View, but add them the following way instead:
VStack{
NavigationView{ ... }
Text("").hidden().sheet(...)
Text("").hidden().sheet(...)
}
SwiftUI automatically sizing bottom sheet
Talk about over engineering the problem. All you have to do is specify a height of 0 if you want the sheet to be hidden, and not specify a height when it's shown. Additionally set the frame alignment to be top.
struct ContentView: View{
@State private var hide = false
var body: some View{
ZStack(alignment: .bottom){
Color.blue
.overlay(
Text("Is hidden : \(hide.description)").foregroundColor(.white)
.padding(.bottom, 200)
)
.onTapGesture{
hide.toggle()
}
VStack(spacing: 0){
ForEach(0..<5){ index in
Rectangle()
.foregroundColor(index.isMultiple(of: 2) ? Color.gray : .orange)
.frame(height: 50)
.layoutPriority(2)
}
}
.layoutPriority(1)
.frame(height: hide ? 0 : nil, alignment: .top)
.animation(.linear(duration: 0.2))
}.edgesIgnoringSafeArea(.all)
}
}
Show bottom sheet after button press swiftui
iOS 16
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)
}
Demo:
Cant get my sheet to display the right information
In iOS 14 sheet(isPresented:content:)
is now created beforehand and the view is not refreshed when the $businessSheetPresented
changes.
Instead of .sheet(isPresented:content:)
.sheet(isPresented: $businessSheetPresented) {
if let pressedUser = pressedUser {
DisplayBusinessSheet(user: pressedUser)
}
}
you should use .sheet(item:content:)
.sheet(item: $pressedUser) { user in
DisplayBusinessSheet(user: pressedUser)
}
Note: with the above solution you don't really need businessSheetPresented
unless it's used in some other place apart from triggering the sheet.
SwiftUI Sheet not updating variable
Create a binding with an optional workout. Nil will be the case that the sheet is not presented, assigning a workout to this variable displays the corresponding sheet.
struct WorkoutList: View {
private var workouts = workoutData
@State var selectedWorkout: Workout?
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(workouts) { workoutCard in
GeometryReader { geometry in
Button(action: { self.selectedWorkout = workoutCard }) {
WorkoutCardView(workout: workoutCard).rotation3DEffect(Angle(degrees: Double((geometry.frame(in: .global).minX - 30) / -30)), axis: (x: 0, y: 10, z: 0)).sheet(item: self.$selectedWorkout) { workout in
WorkoutDetails(workout: workout)
}
}
}.frame(width:246, height: 360)
}
}.padding(30)
}
}
}
Related Topics
Count Elements of Array Matching Condition in Swift
How to Remove Optional from String Value Swift
Ondelete Causing Nsrangeexception
How to Set the Alpha of an Uiimage in Swift Programmatically
Bold Part of String in Uitextview Swift
How to Link a .Sks File to a .Swift File in Spritekit
What's the Equivalent of Finally in Swift
Method' Is Ambiguous for Type Lookup in This Context, Error in Alamofire
Swiftui Background Color of List MAC Os
Swift 2.0 - 'Nil' or '0' Enum Arguments
Beginner Swift Sprite Kit - Node Collision Detection Help (Skphysicscontact)
Global Function Sequence(State:Next:) and Type Inference
How to Pass Variable Value to Outside of Urlsession Async - Swift 3
Could Not Find an Overload for '/' That Accepts the Supplied Arguments
Get the Size (In Bytes) of an Object on the Heap
How to Use Combine to Track Uitextfield Changes in a Uiviewrepresentable Class