SwiftUI multiple sheets: dismiss animation broken - swiftui

I have button1, showing sheet1 when pressed. Then I have a toggle, showing a button2, which in turn presents sheet2 when pressed.
If I just press button1, the sheet shows and dismisses with animation. If, however, I press the toggle, which shows button2, and then press button1, the sheet dismiss animation is broken (simply doesn't animate).
struct ContentView: View {
#State private var showSheetButton2 = false
#State private var showSheet1 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Toggle("Show Sheet Button 2", isOn: $showSheetButton2)
Button(action: {
showSheet1.toggle()
}, label: {
Text("Show Sheet 1")
})
.sheet(isPresented: $showSheet1, content: {
Button(action: {
showSheet1 = false
}, label: {
Text("Dismiss")
})
})
if showSheetButton2 {
Button(action: {
showSheet2.toggle()
}, label: {
Text("Show Sheet 2")
})
.sheet(isPresented: $showSheet2, content: {
Button(action: {
showSheet2 = false
}, label: {
Text("Dismiss")
})
})
}
}.padding()
}
}
EDIT:
Fixed in iOS 14.5:
You can now apply multiple sheet(isPresented:onDismiss:content:) and
fullScreenCover(item:onDismiss:content:) modifiers in the same view
hierarchy. (74246633)

Somehow this sheet inside If statement is destroying animation. Please try to add EmptyView outside this if statement and assign sheet to that view.
if showSheetButton2 {
Button(action: {
showSheet2.toggle()
}, label: {
Text("Show Sheet 2")
})
}
EmptyView()
.sheet(isPresented: $showSheet2, content: {
Button(action: {
showSheet2 = false
}, label: {
Text("Dismiss")
})
})

Related

SwfitUI - Cannot open sheet from a button that is a picker option

I want to open navigate to a view where I can add another picker option.
I tried the followings:
Picker(NSLocalizedString("Pick an option", comment: ""), selection: $value, content: {
ForEach(options, id: \.self){ option in
Text(option).tag(option)
}
Button(action: {
sheetIsPresented.toggle()
}, label: {
Image(systemName: "plus")
})
.sheet(isPresented: $sheetIsPresented){
AddView()
}
})
A button does show at the end of the options.
However, when the button is clicked, nothing happened. AddView() doesn't show up...
Is there a way to make this work?
try this approach, as shown in this example code:
struct ContentView: View {
#State var sheetIsPresented = false
#State var options = ["item-1","item-2","item-3"]
#State var value = "item-1"
var body: some View {
VStack (spacing: 88){
Picker(NSLocalizedString("Pick an option", comment: ""), selection: $value) {
ForEach(options, id: \.self){ option in
Text(option).tag(option)
}
}
Button(action: { sheetIsPresented.toggle() }) {
Image(systemName: "plus")
}
.sheet(isPresented: $sheetIsPresented) {
AddView(options: $options)
}
}
}
}
struct AddView: View {
#Binding var options: [String]
var body: some View {
// add some random option
Button(action: { options.append(UUID().uuidString) }) {
Image(systemName: "plus")
}
}
}
You can open a sheet with a menu if you put the .sheet on the menu element
example:
struct TestView: View {
#State var sheetIsPresented: Bool = false
var body: some View {
Menu("Actions") {
Button(action: {
sheetIsPresented.toggle()
}, label: {
Image(systemName: "plus")
})
}
.sheet(isPresented: $sheetIsPresented){
VStack{
Text("Hello")
}
}
}
}

Picker in Menu with Section and Label doesn't work

The following code shows different Pickers.
I want to achieve: MenuPickerStyle with Sections and custom Label like in second example https://imgur.com/a/Q0uxEmh but it is not selectable because it is grayed out
When doing it with the Menu container as recommended in the internet the rows are grayed out. How can I get the wanted behavior?
In the code you can see 4 different examples. The second is the grayed out picker rows.
Tested in Xcode 13.2.1, iOS 15
struct ContentView: View{
#State var number_1 = 0
#State var number_2 = 0
#State var number_3 = 0
#State var number_4 = 0
let numbers = [0,1,2,3,4]
var body: some View{
VStack{
//Picker works, no Section --------------------------
Menu(content: {
Picker(selection: $number_1, content: {
ForEach(numbers,id: \.self){i in
Text("Number: \(i)").tag(i)
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_1)")
})
//Picker is grayed out ---------------------------
Menu(content: {
Picker(selection: $number_2, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
}, label: {
Text("selected Number = \(number_2)")
})
//Picker works, collapsed View not desired behavior --------
Menu(content: {
Picker(selection: $number_3, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {EmptyView()})
.pickerStyle(MenuPickerStyle())
}, label: {
Text("selected Number = \(number_3)")
})
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
}
}
}
You can put multiple Pickers in a Menu, each with a subset of the available values, and they’ll all work as expected:
Menu {
// First section, with values 0–2
Picker(selection: $num) {
ForEach(0..<3, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
// Second section, with values 3–5
Picker(selection: $num) {
ForEach(3..<6, id: \.self) { i in
Text("Number: \(i)").tag(i)
}
} label: {
EmptyView()
}
} label: {
Text("selected Number = \(num)")
}
SwiftUI will even stick a separator between the Pickers automatically.
The simplest fix is for case 4 - just rebuild menu (force-refresh) on selection changed:
//Picker Works, label not ----------------------------
Picker(selection: $number_4, content: {
ForEach(numbers,id: \.self){i in
Section{
Text("Number: \(i)").tag(i)
}
}
}, label: {Text("selected Number = \(number_4)")})
.pickerStyle(MenuPickerStyle())
.id(number_4) // << here !!
Tested with Xcode 13.4 / iOS 15.5
I think I've got a workaround.
I tried to use a menu and use Buttons for manipulating the selection. The problem is the mark of the selection if there is already a Image in the label of the button I think.
struct ContentView: View{
#State var number = 0
let numbers = [1,2,3,4]
var body: some View{
Menu(content: {
ForEach(numbers,id:\.self){i in
Section{
Button(action: {
number = i
}, label: {
Text("Number: \(i)")
})
}
}
}, label: {
Text("The selection is: \(number)")
.frame(width: UIScreen.main.bounds.width, height: 20)
.border(Color.green)
})
}
}
I'm not 100% certain I understand, but if your goal is to build a picker that lets the user click on the title and then select an option from a menu, returning the selection to the screen I would suggest doing it as follows:
struct SwiftUIView: View {
#State var option:Int = 0
let numbers = [1,2,3,4]
var body: some View {
VStack {
//Picker to select the option
Picker("Option Picker", selection: $option) {
ForEach(numbers, id: \.self) {
number in
Text("Selection Number \(number)").tag(number)
}
}.pickerStyle(MenuPickerStyle())
//Text confirming the selection won't show before a pick is made
Text(option != 0 ? "You selected option number \(option)" : "")
}
}
}
Let me know if that works for you / is what you were looking for.

Weird fullScreenCover and sheet behaviour in iOS 15

I have some problem with presenting sheet, sheet that use Identifiable item binding and fullScreenCover.
Main issue is that:
I choose identifiable item, corresponding sheet appears
I close that sheet with swipe
After it's dismiss i open normal sheet
It open again identifiable item sheet content
Another weird behaviour is if I open fullScreenCover after dismissing sheet it's open sheet but with content of fullScreenCover
This never happens on iOS 14. I think that is states that responsible for presenting those views not have time to update
You can checkout minimal gist reproduction or see it here
import SwiftUI
struct ContentView: View {
enum Sheet: String, Identifiable {
var id: String {
rawValue
}
case one, two
}
#State var isFullScreenPresented: Bool = false
#State var isSheetPresented: Bool = false
#State var isItemSheetPresented: Sheet?
var body: some View {
VStack {
HStack {
Button(action: {isFullScreenPresented.toggle()}, label: {
Text("fullScreen")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isSheetPresented.toggle()}, label: {
Text("sheet")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Button(action: {isItemSheetPresented = .one}, label: {
Text("one")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
Button(action: {isItemSheetPresented = .two}, label: {
Text("two")
.padding()
.foregroundColor(.red)
.background(Rectangle())
})
}
HStack {
Text(isFullScreenPresented.description)
Text(isSheetPresented.description)
}
Text(isItemSheetPresented.debugDescription)
Spacer()
}
.sheet(item: $isItemSheetPresented, onDismiss: {isItemSheetPresented = nil}, content: {item in
Text(item.id)
})
.sheet(isPresented: $isSheetPresented, onDismiss: { isSheetPresented = false}, content: {
Text("sheet")
})
.fullScreenCover(isPresented: $isFullScreenPresented, onDismiss: {isFullScreenPresented = false }, content: {FullScreenContent()})
}
}
struct FullScreenContent: View {
#Environment(\.presentationMode) var dismiss
var body: some View {
VStack {
Button("close", action: {dismiss.wrappedValue.dismiss()})
Spacer()
}
}
}

How to move between views in SwiftUI?

I am trying to open a view on a button click rather than on clicking a Navigation Link, but all I can find on the internet is how to do it with a Navigation Link. So how do you do it without?
Code:
#State var goToMainView = false
NavigationLink(destination: ContentView(),isActive: $goToMainView,label:{}).hidden()
Button(action: {
goToMainView = true
context.delete(goal)
do {
try context.save()
} catch {
print(error)
}
}, label: {
Text("Delete Goal")
})
You can navigate with a button by using NavigationLink
#State var goToSecondView = false
NavigationView {
VStack {
NavigationLink(destination: SecondView(),isActive: $goToSecondView,
label: { EmptyView() })
}.hidden()
Button(action : { goToSecondView = true } , label : { Text("Click On Me") })
}
}
And for more details , I wrote this article , it may help you
How to navigate with SwiftUI

SwiftUI "Push" NavigationView/NavigationLink not working when trying to create multi-screen flow

In SwiftUI, I set up a 4 screen flow (1>2>3>4) where the user would hit "next" on each to navigate to the next screen - just like a typical push flow in UIKit. Im using "programmatic" NavigationLinks (e.g. isActive parameter) for flexibility. It gets to screen 3, but for some reason hitting next on screen 3 doesn't navigate to screen 4. Can't figure it out.
struct FlowView: View {
#State var navigateToScreen2 = false
#State var navigateToScreen3 = false
#State var navigateToScreen4 = false
var body: some View {
NavigationView {
VStack {
Text("Screen 1")
Button(action: { self.navigateToScreen2 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 2")
Button(action: { self.navigateToScreen3 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 3")
Button(action: { self.navigateToScreen4 = true}, label: { Text("Next") })
NavigationLink(destination:
Text("Screen 4"),
isActive: self.$navigateToScreen4,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen3,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen2,
label: { EmptyView() }
)
}
}
}
}
I would do it like this. It works and can be much better read:
struct ContentView: View {
#State private var navigateToScreen2 = false
var body: some View {
NavigationView {
NavigationLink(destination: View2(), isActive: $navigateToScreen2) {
Text("View1")
}
}
}
}
struct View2: View {
#State private var navigateToScreen3 = false
var body: some View {
NavigationLink(destination: View3(), isActive: $navigateToScreen3) {
Text("View2")
}
}
}
struct View3: View {
#State private var navigateToScreen4 = false
var body: some View {
NavigationLink(destination: View4(), isActive: $navigateToScreen4) {
Text("View3")
}
}
}
struct View4: View {
var body: some View {
Text("View4")
}
}